Skip to content

Enforce use of throwing AbsolutePath initializer #353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 62 additions & 31 deletions Sources/TSCBasic/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,13 @@ public protocol FileSystem: AnyObject {
func changeCurrentWorkingDirectory(to path: AbsolutePath) throws

/// Get the home directory of current user
var homeDirectory: AbsolutePath { get }
var homeDirectory: AbsolutePath { get throws }

/// Get the caches directory of current user
var cachesDirectory: AbsolutePath? { get }

/// Get the temp directory
var tempDirectory: AbsolutePath { get }
var tempDirectory: AbsolutePath { get throws }

/// Create the given directory.
func createDirectory(_ path: AbsolutePath) throws
Expand Down Expand Up @@ -317,7 +317,9 @@ private class LocalFileSystem: FileSystem {
}

func isFile(_ path: AbsolutePath) -> Bool {
let path = resolveSymlinks(path)
guard let path = try? resolveSymlinks(path) else {
return false
}
let attrs = try? FileManager.default.attributesOfItem(atPath: path.pathString)
return attrs?[.type] as? FileAttributeType == .typeRegular
}
Expand Down Expand Up @@ -353,7 +355,7 @@ private class LocalFileSystem: FileSystem {
let fsr: UnsafePointer<Int8> = cwdStr.fileSystemRepresentation
defer { fsr.deallocate() }

return try? AbsolutePath(validating: String(cString: fsr))
return try? AbsolutePath(String(cString: fsr))
#endif
}

Expand All @@ -368,19 +370,23 @@ private class LocalFileSystem: FileSystem {
}

var homeDirectory: AbsolutePath {
return AbsolutePath(NSHomeDirectory())
get throws {
return try AbsolutePath(validating: NSHomeDirectory())
}
}

var cachesDirectory: AbsolutePath? {
return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first.flatMap { AbsolutePath($0.path) }
return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first.flatMap { try? AbsolutePath(validating: $0.path) }
}

var tempDirectory: AbsolutePath {
let override = ProcessEnv.vars["TMPDIR"] ?? ProcessEnv.vars["TEMP"] ?? ProcessEnv.vars["TMP"]
if let path = override.flatMap({ try? AbsolutePath(validating: $0) }) {
return path
get throws {
let override = ProcessEnv.vars["TMPDIR"] ?? ProcessEnv.vars["TEMP"] ?? ProcessEnv.vars["TMP"]
if let path = override.flatMap({ try? AbsolutePath(validating: $0) }) {
return path
}
return try AbsolutePath(validating: NSTemporaryDirectory())
}
return AbsolutePath(NSTemporaryDirectory())
}

func getDirectoryContents(_ path: AbsolutePath) throws -> [String] {
Expand Down Expand Up @@ -664,7 +670,7 @@ public class InMemoryFileSystem: FileSystem {
case .directory, .file:
return node
case .symlink(let destination):
let destination = AbsolutePath(destination, relativeTo: path.parentDirectory)
let destination = try AbsolutePath(validating: destination, relativeTo: path.parentDirectory)
return followSymlink ? try getNodeInternal(destination) : node
case .none:
return nil
Expand Down Expand Up @@ -745,24 +751,28 @@ public class InMemoryFileSystem: FileSystem {

/// Virtualized current working directory.
public var currentWorkingDirectory: AbsolutePath? {
return AbsolutePath("/")
return try? AbsolutePath(validating: "/")
}

public func changeCurrentWorkingDirectory(to path: AbsolutePath) throws {
throw FileSystemError(.unsupported, path)
}

public var homeDirectory: AbsolutePath {
// FIXME: Maybe we should allow setting this when creating the fs.
return AbsolutePath("/home/user")
get throws {
// FIXME: Maybe we should allow setting this when creating the fs.
return try AbsolutePath(validating: "/home/user")
}
}

public var cachesDirectory: AbsolutePath? {
return self.homeDirectory.appending(component: "caches")
return try? self.homeDirectory.appending(component: "caches")
}

public var tempDirectory: AbsolutePath {
return AbsolutePath("/tmp")
get throws {
return try AbsolutePath(validating: "/tmp")
}
}

public func getDirectoryContents(_ path: AbsolutePath) throws -> [String] {
Expand Down Expand Up @@ -978,7 +988,7 @@ public class InMemoryFileSystem: FileSystem {
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
let resolvedPath: AbsolutePath = try lock.withLock {
if case let .symlink(destination) = try getNode(path)?.contents {
return AbsolutePath(destination, relativeTo: path.parentDirectory)
return try AbsolutePath(validating: destination, relativeTo: path.parentDirectory)
} else {
return path
}
Expand Down Expand Up @@ -1023,48 +1033,69 @@ public class RerootedFileSystemView: FileSystem {
}

/// Adjust the input path for the underlying file system.
private func formUnderlyingPath(_ path: AbsolutePath) -> AbsolutePath {
private func formUnderlyingPath(_ path: AbsolutePath) throws -> AbsolutePath {
if path == AbsolutePath.root {
return root
} else {
// FIXME: Optimize?
return AbsolutePath(String(path.pathString.dropFirst(1)), relativeTo: root)
return try AbsolutePath(validating: String(path.pathString.dropFirst(1)), relativeTo: root)
}
}

// MARK: FileSystem Implementation

public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool {
return underlyingFileSystem.exists(formUnderlyingPath(path), followSymlink: followSymlink)
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.exists(underlying, followSymlink: followSymlink)
}

public func isDirectory(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isDirectory(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isDirectory(underlying)
}

public func isFile(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isFile(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isFile(underlying)
}

public func isSymlink(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isSymlink(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isSymlink(underlying)
}

public func isReadable(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isReadable(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isReadable(underlying)
}

public func isWritable(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isWritable(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isWritable(underlying)
}

public func isExecutableFile(_ path: AbsolutePath) -> Bool {
return underlyingFileSystem.isExecutableFile(formUnderlyingPath(path))
guard let underlying = try? formUnderlyingPath(path) else {
return false
}
return underlyingFileSystem.isExecutableFile(underlying)
}

/// Virtualized current working directory.
public var currentWorkingDirectory: AbsolutePath? {
return AbsolutePath("/")
return try? AbsolutePath(validating: "/")
}

public func changeCurrentWorkingDirectory(to path: AbsolutePath) throws {
Expand All @@ -1088,13 +1119,13 @@ public class RerootedFileSystemView: FileSystem {
}

public func createDirectory(_ path: AbsolutePath, recursive: Bool) throws {
let path = formUnderlyingPath(path)
let path = try formUnderlyingPath(path)
return try underlyingFileSystem.createDirectory(path, recursive: recursive)
}

public func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws {
let path = formUnderlyingPath(path)
let destination = formUnderlyingPath(destination)
let path = try formUnderlyingPath(path)
let destination = try formUnderlyingPath(destination)
return try underlyingFileSystem.createSymbolicLink(path, pointingAt: destination, relative: relative)
}

Expand All @@ -1103,7 +1134,7 @@ public class RerootedFileSystemView: FileSystem {
}

public func writeFileContents(_ path: AbsolutePath, bytes: ByteString) throws {
let path = formUnderlyingPath(path)
let path = try formUnderlyingPath(path)
return try underlyingFileSystem.writeFileContents(path, bytes: bytes)
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/TSCBasic/Lock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,15 @@ public final class FileLock {

public static func withLock<T>(fileToLock: AbsolutePath, lockFilesDirectory: AbsolutePath? = nil, type: LockType = .exclusive, body: () throws -> T) throws -> T {
// unless specified, we use the tempDirectory to store lock files
let lockFilesDirectory = lockFilesDirectory ?? localFileSystem.tempDirectory
let lockFilesDirectory = try lockFilesDirectory ?? localFileSystem.tempDirectory
if !localFileSystem.exists(lockFilesDirectory) {
throw FileSystemError(.noEntry, lockFilesDirectory)
}
if !localFileSystem.isDirectory(lockFilesDirectory) {
throw FileSystemError(.notDirectory, lockFilesDirectory)
}
// use the parent path to generate unique filename in temp
var lockFileName = (resolveSymlinks(fileToLock.parentDirectory)
var lockFileName = try (resolveSymlinks(fileToLock.parentDirectory)
.appending(component: fileToLock.basename))
.components.joined(separator: "_")
.replacingOccurrences(of: ":", with: "_") + ".lock"
Expand Down
33 changes: 21 additions & 12 deletions Sources/TSCBasic/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,12 @@ public struct AbsolutePath: Hashable {
_impl = impl
}

/// Initializes the AbsolutePath from `absStr`, which must be an absolute
/// path (i.e. it must begin with a path separator; this initializer does
/// not interpret leading `~` characters as home directory specifiers).
/// The input string will be normalized if needed, as described in the
/// documentation for AbsolutePath.
public init(_ absStr: String) {
self.init(PathImpl(normalizingAbsolutePath: absStr))
}

/// Initializes an AbsolutePath from a string that may be either absolute
/// or relative; if relative, `basePath` is used as the anchor; if absolute,
/// it is used as is, and in this case `basePath` is ignored.
public init(_ str: String, relativeTo basePath: AbsolutePath) {
public init(validating str: String, relativeTo basePath: AbsolutePath) throws {
if PathImpl(string: str).isAbsolute {
self.init(str)
try self.init(validating: str)
} else {
#if os(Windows)
assert(!basePath.pathString.isEmpty)
Expand Down Expand Up @@ -115,7 +106,11 @@ public struct AbsolutePath: Hashable {
self.init(absPath, RelativePath(relStr))
}

/// Convenience initializer that verifies that the path is absolute.
/// Initializes the AbsolutePath from `absStr`, which must be an absolute
/// path (i.e. it must begin with a path separator; this initializer does
/// not interpret leading `~` characters as home directory specifiers).
/// The input string will be normalized if needed, as described in the
/// documentation for AbsolutePath.
public init(validating path: String) throws {
try self.init(PathImpl(validatingAbsolutePath: path))
}
Expand Down Expand Up @@ -1065,3 +1060,17 @@ private func mayNeedNormalization(absolute string: String) -> Bool {
}
return false
}

// MARK: - `AbsolutePath` backwards compatibility, delete after deprecation period.

extension AbsolutePath {
@available(*, deprecated, message: "use throwing variant instead")
public init(_ absStr: String) {
try! self.init(validating: absStr)
}

@available(*, deprecated, message: "use throwing variant instead")
public init(_ str: String, relativeTo basePath: AbsolutePath) {
try! self.init(validating: str, relativeTo: basePath)
}
}
15 changes: 4 additions & 11 deletions Sources/TSCBasic/PathShims.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ import TSCLibc
import Foundation

/// Returns the "real path" corresponding to `path` by resolving any symbolic links.
public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath {
public func resolveSymlinks(_ path: AbsolutePath) throws -> AbsolutePath {
#if os(Windows)
let handle: HANDLE = path.pathString.withCString(encodedAs: UTF16.self) {
CreateFileW($0, GENERIC_READ, DWORD(FILE_SHARE_READ), nil,
DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil)
}
if handle == INVALID_HANDLE_VALUE { return path }
defer { CloseHandle(handle) }
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: 261) {
return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: 261) {
let dwLength: DWORD =
GetFinalPathNameByHandleW(handle, $0.baseAddress!, DWORD($0.count),
DWORD(FILE_NAME_NORMALIZED))
let path = String(decodingCString: $0.baseAddress!, as: UTF16.self)
return try! AbsolutePath(validating: path)
return try AbsolutePath(path)
}
#else
let pathStr = path.pathString
Expand All @@ -51,7 +51,7 @@ public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath {
// null-terminated UTF-8 data referenced by the given pointer.
resultPtr.deallocate()
// FIXME: We should measure if it's really more efficient to compare the strings first.
return result == pathStr ? path : AbsolutePath(result)
return result == pathStr ? path : try AbsolutePath(validating: result)
}

return path
Expand All @@ -72,13 +72,6 @@ public func createSymlink(_ path: AbsolutePath, pointingAt dest: AbsolutePath, r
try FileManager.default.createSymbolicLink(atPath: path.pathString, withDestinationPath: destString)
}

/// The current working directory of the processs.
@available(*, deprecated, renamed: "localFileSystem.currentWorkingDirectory")
public var currentWorkingDirectory: AbsolutePath {
let cwdStr = FileManager.default.currentDirectoryPath
return AbsolutePath(cwdStr)
}

/**
- Returns: a generator that walks the specified directory producing all
files therein. If recursively is true will enter any directories
Expand Down
10 changes: 5 additions & 5 deletions Sources/TSCBasic/TemporaryFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private extension TempFileError {
///
/// - Returns: Path to directory in which temporary file should be created.
public func determineTempDirectory(_ dir: AbsolutePath? = nil) throws -> AbsolutePath {
let tmpDir = dir ?? localFileSystem.tempDirectory
let tmpDir = try dir ?? localFileSystem.tempDirectory
guard localFileSystem.isDirectory(tmpDir) else {
throw TempFileError.couldNotFindTmpDir(tmpDir.pathString)
}
Expand Down Expand Up @@ -87,7 +87,7 @@ public struct TemporaryFile {
// Determine in which directory to create the temporary file.
self.dir = try determineTempDirectory(dir)
// Construct path to the temporary file.
let path = AbsolutePath(prefix + ".XXXXXX" + suffix, relativeTo: self.dir)
let path = try AbsolutePath(validating: prefix + ".XXXXXX" + suffix, relativeTo: self.dir)

// Convert path to a C style string terminating with null char to be an valid input
// to mkstemps method. The XXXXXX in this string will be replaced by a random string
Expand All @@ -98,7 +98,7 @@ public struct TemporaryFile {
// If mkstemps failed then throw error.
if fd == -1 { throw TempFileError(errno: errno) }

self.path = AbsolutePath(String(cString: template))
self.path = try AbsolutePath(validating: String(cString: template))
fileHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
}
}
Expand Down Expand Up @@ -236,7 +236,7 @@ public func withTemporaryDirectory<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory" , _ body: (AbsolutePath, @escaping (AbsolutePath) -> Void) throws -> Result
) throws -> Result {
// Construct path to the temporary directory.
let templatePath = try AbsolutePath(prefix + ".XXXXXX", relativeTo: determineTempDirectory(dir))
let templatePath = try AbsolutePath(validating: prefix + ".XXXXXX", relativeTo: determineTempDirectory(dir))

// Convert templatePath to a C style string terminating with null char to be an valid input
// to mkdtemp method. The XXXXXX in this string will be replaced by a random string
Expand All @@ -247,7 +247,7 @@ public func withTemporaryDirectory<Result>(
throw MakeDirectoryError(errno: errno)
}

return try body(AbsolutePath(String(cString: template))) { path in
return try body(AbsolutePath(validating: String(cString: template))) { path in
_ = try? FileManager.default.removeItem(atPath: path.pathString)
}
}
Expand Down
Loading