From d7ec183b821ead45a0a913bf397aa566f9b932f6 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 11:32:29 +0800 Subject: [PATCH 01/15] Reimplement Path API with SwiftSystem --- CMakeLists.txt | 4 + Package.resolved | 16 + Package.swift | 7 +- Sources/TSCBasic/FileSystem.swift | 4 +- Sources/TSCBasic/Path.swift | 1025 ++++++----------------- Sources/TSCBasic/PathShims.swift | 5 +- Tests/TSCBasicTests/PathShimTests.swift | 6 +- Tests/TSCBasicTests/PathTests.swift | 40 +- 8 files changed, 304 insertions(+), 803 deletions(-) create mode 100644 Package.resolved diff --git a/CMakeLists.txt b/CMakeLists.txt index e4abb990..abbe230d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(BUILD_SHARED_LIBS "Build shared libraries by default" YES) +if(FIND_PM_DEPS) + find_package(SwiftSystem CONFIG REQUIRED) +endif() + find_package(dispatch QUIET) find_package(Foundation QUIET) find_package(Threads QUIET) diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..ebd3ef7f --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-system", + "repositoryURL": "https://github.com/apple/swift-system.git", + "state": { + "branch": null, + "revision": "2bc160bfe34d843ae5ff47168080add24dfd7eac", + "version": "0.0.2" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 94b567be..80a5360e 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,9 @@ let package = Package( name: "TSCTestSupport", targets: ["TSCTestSupport"]), ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "0.0.1")) + ], targets: [ // MARK: Tools support core targets @@ -44,7 +46,8 @@ let package = Package( .target( /** TSCBasic support library */ name: "TSCBasic", - dependencies: ["TSCLibc", "TSCclibc"]), + dependencies: ["TSCLibc", "TSCclibc", + .product(name: "SystemPackage", package: "swift-system")]), .target( /** Abstractions for common operations, should migrate to TSCBasic */ name: "TSCUtility", diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index b15a2d6c..05e9e119 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -377,7 +377,7 @@ private class LocalFileSystem: FileSystem { } func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws { - let destString = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString + let destString = relative ? try destination.relative(to: path.parentDirectory).pathString : destination.pathString try FileManager.default.createSymbolicLink(atPath: path.pathString, withDestinationPath: destString) } @@ -803,7 +803,7 @@ public class InMemoryFileSystem: FileSystem { throw FileSystemError(.alreadyExistsAtDestination, path) } - let destination = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString + let destination = relative ? try destination.relative(to: path.parentDirectory).pathString : destination.pathString contents.entries[path.basename] = Node(.symlink(destination)) } diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index b13b0034..6c556bbf 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -1,26 +1,173 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information See http://swift.org/CONTRIBUTORS.txt for Swift project authors */ -#if os(Windows) -import Foundation -import WinSDK -#endif - -#if os(Windows) -private typealias PathImpl = UNIXPath -#else -private typealias PathImpl = UNIXPath -#endif +import SystemPackage import protocol Foundation.CustomNSError import var Foundation.NSLocalizedDescriptionKey +public protocol Path: Hashable, Codable, CustomStringConvertible { + /// Underlying type, based on SwiftSystem. + var filepath: FilePath { get } + + /// Public initializer from FilePath. + init(_ filepath: FilePath) + + /// Public initializer from String. + init(_ string: String) + + /// Convenience initializer that verifies that the path lexically. + init(validating path: String) throws + + /// Normalized string representation (the normalization rules are described + /// in the documentation of the initializer). This string is never empty. + var pathString: String { get } + + /// The root of a path. + var root: String? { get } + + /// Directory component. An absolute path always has a non-empty directory + /// component (the directory component of the root path is the root itself). + var dirname: String { get } + + /// Last path component (including the suffix, if any). + var basename: String { get } + + /// Returns the basename without the extension. + var basenameWithoutExt: String { get } + + /// Extension of the give path's basename. This follow same rules as + /// suffix except that it doesn't include leading `.` character. + var `extension`: String? { get } + + /// Suffix (including leading `.` character) if any. Note that a basename + /// that starts with a `.` character is not considered a suffix, nor is a + /// trailing `.` character. + var suffix: String? { get } + + /// True if the path is a root directory. + var isRoot: Bool { get } + + /// Returns the path with an additional literal component appended. + /// + /// This method accepts pseudo-path like '.' or '..', but should not contain "/". + func appending(component: String) -> Self + + /// Returns the relative path with additional literal components appended. + /// + /// This method should only be used in cases where the input is guaranteed + /// to be a valid path component (i.e., it cannot be empty, contain a path + /// separator, or be a pseudo-path like '.' or '..'). + func appending(components names: [String]) -> Self + func appending(components names: String...) -> Self + + /// Returns an array of strings that make up the path components of the + /// path. This is the same sequence of strings as the basenames of each + /// successive path component. + /// + /// An empty path has a single path + /// component: the `.` string. + var components: [String] { get } +} + +extension Path { + public var pathString: String { + if filepath.string.isEmpty { + return "." + } + return filepath.string + } + + public var root: String? { + return filepath.root?.string + } + + public var dirname: String { + let dirname = filepath.removingLastComponent().string + if dirname.isEmpty { + return "." + } + return dirname + } + + public var basename: String { + return filepath.lastComponent?.string ?? root ?? "." + } + + public var basenameWithoutExt: String { + return filepath.lastComponent?.stem ?? root ?? "." + } + + public var `extension`: String? { + guard let ext = filepath.extension, + !ext.isEmpty else { + return nil + } + return filepath.extension + } + + public var suffix: String? { + if let ext = `extension` { + return ".\(ext)" + } else { + return nil + } + } + + public var isRoot: Bool { + return filepath.isRoot + } + + public func appending(component: String) -> Self { + return Self(filepath.appending( + FilePath.Component(stringLiteral: component))) + } + + public func appending(components names: [String]) -> Self { + let components = names.map(FilePath.Component.init) + return Self(filepath.appending(components)) + } + + public func appending(components names: String...) -> Self { + appending(components: names) + } + + public var components: [String] { + var components = filepath.components.map(\.string) + if filepath.isRelative && components.isEmpty { + components.append(".") + } + return components + } + + public var description: String { + return pathString + } + + public var debugDescription: String { + // FIXME: We should really be escaping backslashes and quotes here. + return "<\(Self.self):\"\(pathString)\">" + } +} + +extension Path { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(pathString) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + try self.init(validating: container.decode(String.self)) + } +} + /// Represents an absolute file system path, independently of what (or whether /// anything at all) exists at that path in the file system at any given time. /// An absolute path always starts with a `/` character, and holds a normalized @@ -41,22 +188,14 @@ import var Foundation.NSLocalizedDescriptionKey /// normalization, because it is normally the responsibility of the shell and /// not the program being invoked (e.g. when invoking `cd ~`, it is the shell /// that evaluates the tilde; the `cd` command receives an absolute path). -public struct AbsolutePath: Hashable { - /// Check if the given name is a valid individual path component. - /// - /// This only checks with regard to the semantics enforced by `AbsolutePath` - /// and `RelativePath`; particular file systems may have their own - /// additional requirements. - static func isValidComponent(_ name: String) -> Bool { - return PathImpl.isValidComponent(name) - } - - /// Private implementation details, shared with the RelativePath struct. - private let _impl: PathImpl +public struct AbsolutePath: Path { + /// Underlying type, based on SwiftSystem. + public let filepath: FilePath - /// Private initializer when the backing storage is known. - private init(_ impl: PathImpl) { - _impl = impl + /// Public initializer with FilePath. + public init(_ filepath: FilePath) { + precondition(filepath.isAbsolute) + self.filepath = filepath.lexicallyNormalized() } /// Initializes the AbsolutePath from `absStr`, which must be an absolute @@ -65,82 +204,37 @@ public struct AbsolutePath: Hashable { /// The input string will be normalized if needed, as described in the /// documentation for AbsolutePath. public init(_ absStr: String) { - self.init(PathImpl(normalizingAbsolutePath: absStr)) + self.init(FilePath(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) { - if PathImpl(string: str).isAbsolute { - self.init(str) - } else { - self.init(basePath, RelativePath(str)) - } + self.init(basePath.filepath.pushing(FilePath(str))) } /// Initializes the AbsolutePath by concatenating a relative path to an /// existing absolute path, and renormalizing if necessary. public init(_ absPath: AbsolutePath, _ relPath: RelativePath) { - self.init(absPath._impl.appending(relativePath: relPath._impl)) + self.init(absPath.filepath.pushing(relPath.filepath)) } /// Convenience initializer that appends a string to a relative path. public init(_ absPath: AbsolutePath, _ relStr: String) { - self.init(absPath, RelativePath(relStr)) + self.init(absPath.filepath.pushing(FilePath(relStr))) } /// Convenience initializer that verifies that the path is absolute. public init(validating path: String) throws { - try self.init(PathImpl(validatingAbsolutePath: path)) - } - - /// Directory component. An absolute path always has a non-empty directory - /// component (the directory component of the root path is the root itself). - public var dirname: String { - return _impl.dirname - } - - /// Last path component (including the suffix, if any). it is never empty. - public var basename: String { - return _impl.basename - } - - /// Returns the basename without the extension. - public var basenameWithoutExt: String { - if let ext = self.extension { - return String(basename.dropLast(ext.count + 1)) - } - return basename - } - - /// Suffix (including leading `.` character) if any. Note that a basename - /// that starts with a `.` character is not considered a suffix, nor is a - /// trailing `.` character. - public var suffix: String? { - return _impl.suffix - } - - /// Extension of the give path's basename. This follow same rules as - /// suffix except that it doesn't include leading `.` character. - public var `extension`: String? { - return _impl.extension + try self.init(FilePath(validatingAbsolutePath: path)) } /// Absolute path of parent directory. This always returns a path, because /// every directory has a parent (the parent directory of the root directory /// is considered to be the root directory itself). public var parentDirectory: AbsolutePath { - return AbsolutePath(_impl.parentDirectory) - } - - /// True if the path is the root directory. - public var isRoot: Bool { -#if os(Windows) - return _impl.string.withCString(encodedAs: UTF16.self, PathCchIsRoot) -#else - return _impl == PathImpl.root -#endif + return AbsolutePath(filepath.removingLastComponent()) } /// Returns the absolute path with the relative path applied. @@ -148,29 +242,6 @@ public struct AbsolutePath: Hashable { return AbsolutePath(self, subpath) } - /// Returns the absolute path with an additional literal component appended. - /// - /// This method accepts pseudo-path like '.' or '..', but should not contain "/". - public func appending(component: String) -> AbsolutePath { - return AbsolutePath(_impl.appending(component: component)) - } - - /// Returns the absolute path with additional literal components appended. - /// - /// This method should only be used in cases where the input is guaranteed - /// to be a valid path component (i.e., it cannot be empty, contain a path - /// separator, or be a pseudo-path like '.' or '..'). - public func appending(components names: [String]) -> AbsolutePath { - // FIXME: This doesn't seem a particularly efficient way to do this. - return names.reduce(self, { path, name in - path.appending(component: name) - }) - } - - public func appending(components names: String...) -> AbsolutePath { - appending(components: names) - } - /// NOTE: We will most likely want to add other `appending()` methods, such /// as `appending(suffix:)`, and also perhaps `replacing()` methods, /// such as `replacing(suffix:)` or `replacing(basename:)` for some @@ -179,25 +250,25 @@ public struct AbsolutePath: Hashable { /// NOTE: We may want to consider adding operators such as `+` for appending /// a path component. - /// NOTE: We will want to add a method to return the lowest common ancestor - /// path. - - /// Root directory (whose string representation is just a path separator). - public static let root = AbsolutePath(PathImpl.root) - - /// Normalized string representation (the normalization rules are described - /// in the documentation of the initializer). This string is never empty. - public var pathString: String { - return _impl.string + /// Returns the lowest common ancestor path. + public func lowestCommonAncestor(with path: AbsolutePath) -> AbsolutePath? { + guard root == path.root else { + return nil + } + var filepath = path.filepath + while (!filepath.isRoot) { + if self.filepath.starts(with: filepath) { + break + } + filepath.removeLastComponent() + } + return AbsolutePath(filepath) } - /// Returns an array of strings that make up the path components of the - /// absolute path. This is the same sequence of strings as the basenames - /// of each successive path component, starting from the root. Therefore - /// the first path component of an absolute path is always `/`. - public var components: [String] { - return _impl.components - } + /// Root directory (whose string representation is just a path separator). + /// + /// FIXME: root is not a static value, we'd better remove this property. + public static let root = AbsolutePath(FilePath._root) } /// Represents a relative file system path. A relative path never starts with @@ -215,13 +286,14 @@ public struct AbsolutePath: Hashable { /// This string manipulation may change the meaning of a path if any of the /// path components are symbolic links on disk. However, the file system is /// never accessed in any way when initializing a RelativePath. -public struct RelativePath: Hashable { - /// Private implementation details, shared with the AbsolutePath struct. - fileprivate let _impl: PathImpl +public struct RelativePath: Path { + /// Underlying type, based on SwiftSystem. + public let filepath: FilePath - /// Private initializer when the backing storage is known. - private init(_ impl: PathImpl) { - _impl = impl + /// Public initializer with FilePath. + public init(_ filepath: FilePath) { + precondition(filepath.isRelative) + self.filepath = filepath.lexicallyNormalized() } /// Initializes the RelativePath from `str`, which must be a relative path @@ -230,112 +302,18 @@ public struct RelativePath: Hashable { /// character. The input string will be normalized if needed, as described /// in the documentation for RelativePath. public init(_ string: String) { - // Normalize the relative string and store it as our Path. - self.init(PathImpl(normalizingRelativePath: string)) + self.init(FilePath(string)) } /// Convenience initializer that verifies that the path is relative. public init(validating path: String) throws { - try self.init(PathImpl(validatingRelativePath: path)) - } - - /// Directory component. For a relative path without any path separators, - /// this is the `.` string instead of the empty string. - public var dirname: String { - return _impl.dirname - } - - /// Last path component (including the suffix, if any). It is never empty. - public var basename: String { - return _impl.basename - } - - /// Returns the basename without the extension. - public var basenameWithoutExt: String { - if let ext = self.extension { - return String(basename.dropLast(ext.count + 1)) - } - return basename - } - - /// Suffix (including leading `.` character) if any. Note that a basename - /// that starts with a `.` character is not considered a suffix, nor is a - /// trailing `.` character. - public var suffix: String? { - return _impl.suffix - } - - /// Extension of the give path's basename. This follow same rules as - /// suffix except that it doesn't include leading `.` character. - public var `extension`: String? { - return _impl.extension - } - - /// Normalized string representation (the normalization rules are described - /// in the documentation of the initializer). This string is never empty. - public var pathString: String { - return _impl.string - } - - /// Returns an array of strings that make up the path components of the - /// relative path. This is the same sequence of strings as the basenames - /// of each successive path component. Therefore the returned array of - /// path components is never empty; even an empty path has a single path - /// component: the `.` string. - public var components: [String] { - return _impl.components + try self.init(FilePath(validatingRelativePath: path)) } /// Returns the relative path with the given relative path applied. public func appending(_ subpath: RelativePath) -> RelativePath { - return RelativePath(_impl.appending(relativePath: subpath._impl)) - } - - /// Returns the relative path with an additional literal component appended. - /// - /// This method accepts pseudo-path like '.' or '..', but should not contain "/". - public func appending(component: String) -> RelativePath { - return RelativePath(_impl.appending(component: component)) - } - - /// Returns the relative path with additional literal components appended. - /// - /// This method should only be used in cases where the input is guaranteed - /// to be a valid path component (i.e., it cannot be empty, contain a path - /// separator, or be a pseudo-path like '.' or '..'). - public func appending(components names: [String]) -> RelativePath { - // FIXME: This doesn't seem a particularly efficient way to do this. - return names.reduce(self, { path, name in - path.appending(component: name) - }) - } - - public func appending(components names: String...) -> RelativePath { - appending(components: names) - } -} - -extension AbsolutePath: Codable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(pathString) - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - try self.init(validating: container.decode(String.self)) - } -} - -extension RelativePath: Codable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(pathString) - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - try self.init(validating: container.decode(String.self)) + return + RelativePath(filepath.pushing(subpath.filepath)) } } @@ -346,509 +324,22 @@ extension AbsolutePath: Comparable { } } -/// Make absolute paths CustomStringConvertible and CustomDebugStringConvertible. -extension AbsolutePath: CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - return pathString - } - - public var debugDescription: String { - // FIXME: We should really be escaping backslashes and quotes here. - return "" - } -} - -/// Make relative paths CustomStringConvertible and CustomDebugStringConvertible. -extension RelativePath: CustomStringConvertible { - public var description: String { - return _impl.string - } - - public var debugDescription: String { - // FIXME: We should really be escaping backslashes and quotes here. - return "" - } -} - -/// Private implementation shared between AbsolutePath and RelativePath. -protocol Path: Hashable { - - /// Root directory. - static var root: Self { get } - - /// Checks if a string is a valid component. - static func isValidComponent(_ name: String) -> Bool - - /// Normalized string of the (absolute or relative) path. Never empty. - var string: String { get } - - /// Returns whether the path is an absolute path. - var isAbsolute: Bool { get } - - /// Returns the directory part of the stored path (relying on the fact that it has been normalized). Returns a - /// string consisting of just `.` if there is no directory part (which is the case if and only if there is no path - /// separator). - var dirname: String { get } - - /// Returns the last past component. - var basename: String { get } - - /// Returns the components of the path between each path separator. - var components: [String] { get } - - /// Path of parent directory. This always returns a path, because every directory has a parent (the parent - /// directory of the root directory is considered to be the root directory itself). - var parentDirectory: Self { get } - - /// Creates a path from its normalized string representation. - init(string: String) - - /// Creates a path from an absolute string representation and normalizes it. - init(normalizingAbsolutePath: String) - - /// Creates a path from an relative string representation and normalizes it. - init(normalizingRelativePath: String) - - /// Creates a path from a string representation, validates that it is a valid absolute path and normalizes it. - init(validatingAbsolutePath: String) throws - - /// Creates a path from a string representation, validates that it is a valid relative path and normalizes it. - init(validatingRelativePath: String) throws - - /// Returns suffix with leading `.` if withDot is true otherwise without it. - func suffix(withDot: Bool) -> String? - - /// Returns a new Path by appending the path component. - func appending(component: String) -> Self - - /// Returns a path by concatenating a relative path and renormalizing if necessary. - func appending(relativePath: Self) -> Self -} - -extension Path { - var suffix: String? { - return suffix(withDot: true) - } - - var `extension`: String? { - return suffix(withDot: false) - } -} - -private struct UNIXPath: Path { - let string: String - - static let root = UNIXPath(string: "/") - - static func isValidComponent(_ name: String) -> Bool { - return name != "" && name != "." && name != ".." && !name.contains("/") - } - -#if os(Windows) - static func isAbsolutePath(_ path: String) -> Bool { - return !path.withCString(encodedAs: UTF16.self, PathIsRelativeW) - } -#endif - - var dirname: String { -#if os(Windows) - let fsr: UnsafePointer = string.fileSystemRepresentation - defer { fsr.deallocate() } - - let path: String = String(cString: fsr) - return path.withCString(encodedAs: UTF16.self) { - let data = UnsafeMutablePointer(mutating: $0) - PathCchRemoveFileSpec(data, path.count) - return String(decodingCString: data, as: UTF16.self) - } -#else - // FIXME: This method seems too complicated; it should be simplified, - // if possible, and certainly optimized (using UTF8View). - // Find the last path separator. - guard let idx = string.lastIndex(of: "/") else { - // No path separators, so the directory name is `.`. - return "." - } - // Check if it's the only one in the string. - if idx == string.startIndex { - // Just one path separator, so the directory name is `/`. - return "/" - } - // Otherwise, it's the string up to (but not including) the last path - // separator. - return String(string.prefix(upTo: idx)) -#endif - } - - var isAbsolute: Bool { -#if os(Windows) - return UNIXPath.isAbsolutePath(string) -#else - return string.hasPrefix("/") -#endif - } - - var basename: String { -#if os(Windows) - let path: String = self.string - return path.withCString(encodedAs: UTF16.self) { - PathStripPathW(UnsafeMutablePointer(mutating: $0)) - return String(decodingCString: $0, as: UTF16.self) - } -#else - // FIXME: This method seems too complicated; it should be simplified, - // if possible, and certainly optimized (using UTF8View). - // Check for a special case of the root directory. - if string.spm_only == "/" { - // Root directory, so the basename is a single path separator (the - // root directory is special in this regard). - return "/" - } - // Find the last path separator. - guard let idx = string.lastIndex(of: "/") else { - // No path separators, so the basename is the whole string. - return string - } - // Otherwise, it's the string from (but not including) the last path - // separator. - return String(string.suffix(from: string.index(after: idx))) -#endif - } - - // FIXME: We should investigate if it would be more efficient to instead - // return a path component iterator that does all its work lazily, moving - // from one path separator to the next on-demand. - // - var components: [String] { -#if os(Windows) - return string.components(separatedBy: "\\").filter { !$0.isEmpty } -#else - // FIXME: This isn't particularly efficient; needs optimization, and - // in fact, it might well be best to return a custom iterator so we - // don't have to allocate everything up-front. It would be backed by - // the path string and just return a slice at a time. - let components = string.components(separatedBy: "/").filter({ !$0.isEmpty }) - - if string.hasPrefix("/") { - return ["/"] + components - } else { - return components - } -#endif - } - - var parentDirectory: UNIXPath { - return self == .root ? self : Self(string: dirname) - } - - init(string: String) { - self.string = string - } - - init(normalizingAbsolutePath path: String) { - #if os(Windows) - var buffer: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) - _ = path.withCString(encodedAs: UTF16.self) { - PathCanonicalizeW(&buffer, $0) - } - self.init(string: String(decodingCString: buffer, as: UTF16.self)) - #else - precondition(path.first == "/", "Failure normalizing \(path), absolute paths should start with '/'") - - // At this point we expect to have a path separator as first character. - assert(path.first == "/") - // Fast path. - if !mayNeedNormalization(absolute: path) { - self.init(string: path) - } - - // Split the character array into parts, folding components as we go. - // As we do so, we count the number of characters we'll end up with in - // the normalized string representation. - var parts: [String] = [] - var capacity = 0 - for part in path.split(separator: "/") { - switch part.count { - case 0: - // Ignore empty path components. - continue - case 1 where part.first == ".": - // Ignore `.` path components. - continue - case 2 where part.first == "." && part.last == ".": - // If there's a previous part, drop it; otherwise, do nothing. - if let prev = parts.last { - parts.removeLast() - capacity -= prev.count - } - default: - // Any other component gets appended. - parts.append(String(part)) - capacity += part.count - } - } - capacity += max(parts.count, 1) - - // Create an output buffer using the capacity we've calculated. - // FIXME: Determine the most efficient way to reassemble a string. - var result = "" - result.reserveCapacity(capacity) - - // Put the normalized parts back together again. - var iter = parts.makeIterator() - result.append("/") - if let first = iter.next() { - result.append(contentsOf: first) - while let next = iter.next() { - result.append("/") - result.append(contentsOf: next) - } - } - - // Sanity-check the result (including the capacity we reserved). - assert(!result.isEmpty, "unexpected empty string") - assert(result.count == capacity, "count: " + - "\(result.count), cap: \(capacity)") - - // Use the result as our stored string. - self.init(string: result) - #endif - } - - init(normalizingRelativePath path: String) { - #if os(Windows) - var buffer: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH + 1)) - _ = path.replacingOccurrences(of: "/", with: "\\").withCString(encodedAs: UTF16.self) { - PathCanonicalizeW(&buffer, $0) - } - self.init(string: String(decodingCString: buffer, as: UTF16.self)) - #else - precondition(path.first != "/") - - // FIXME: Here we should also keep track of whether anything actually has - // to be changed in the string, and if not, just return the existing one. - - // Split the character array into parts, folding components as we go. - // As we do so, we count the number of characters we'll end up with in - // the normalized string representation. - var parts: [String] = [] - var capacity = 0 - for part in path.split(separator: "/") { - switch part.count { - case 0: - // Ignore empty path components. - continue - case 1 where part.first == ".": - // Ignore `.` path components. - continue - case 2 where part.first == "." && part.last == ".": - // If at beginning, fall through to treat the `..` literally. - guard let prev = parts.last else { - fallthrough - } - // If previous component is anything other than `..`, drop it. - if !(prev.count == 2 && prev.first == "." && prev.last == ".") { - parts.removeLast() - capacity -= prev.count - continue - } - // Otherwise, fall through to treat the `..` literally. - fallthrough - default: - // Any other component gets appended. - parts.append(String(part)) - capacity += part.count - } - } - capacity += max(parts.count - 1, 0) - - // Create an output buffer using the capacity we've calculated. - // FIXME: Determine the most efficient way to reassemble a string. - var result = "" - result.reserveCapacity(capacity) - - // Put the normalized parts back together again. - var iter = parts.makeIterator() - if let first = iter.next() { - result.append(contentsOf: first) - while let next = iter.next() { - result.append("/") - result.append(contentsOf: next) - } - } - - // Sanity-check the result (including the capacity we reserved). - assert(result.count == capacity, "count: " + - "\(result.count), cap: \(capacity)") - - // If the result is empty, return `.`, otherwise we return it as a string. - self.init(string: result.isEmpty ? "." : result) - #endif - } - - init(validatingAbsolutePath path: String) throws { - #if os(Windows) - let fsr: UnsafePointer = path.fileSystemRepresentation - defer { fsr.deallocate() } - - let realpath = String(cString: fsr) - if !UNIXPath.isAbsolutePath(realpath) { - throw PathValidationError.invalidAbsolutePath(path) - } - self.init(normalizingAbsolutePath: path) - #else - switch path.first { - case "/": - self.init(normalizingAbsolutePath: path) - case "~": - throw PathValidationError.startsWithTilde(path) - default: - throw PathValidationError.invalidAbsolutePath(path) - } - #endif - } - - init(validatingRelativePath path: String) throws { - #if os(Windows) - let fsr: UnsafePointer = path.fileSystemRepresentation - defer { fsr.deallocate() } - - let realpath: String = String(cString: fsr) - if UNIXPath.isAbsolutePath(realpath) { - throw PathValidationError.invalidRelativePath(path) - } - self.init(normalizingRelativePath: path) - #else - switch path.first { - case "/", "~": - throw PathValidationError.invalidRelativePath(path) - default: - self.init(normalizingRelativePath: path) - } - #endif - } - - func suffix(withDot: Bool) -> String? { -#if os(Windows) - let ext = self.string.withCString(encodedAs: UTF16.self) { - PathFindExtensionW($0) - } - var result = String(decodingCString: ext!, as: UTF16.self) - guard result.length > 0 else { return nil } - if !withDot { result.removeFirst(1) } - return result -#else - // FIXME: This method seems too complicated; it should be simplified, - // if possible, and certainly optimized (using UTF8View). - // Find the last path separator, if any. - let sIdx = string.lastIndex(of: "/") - // Find the start of the basename. - let bIdx = (sIdx != nil) ? string.index(after: sIdx!) : string.startIndex - // Find the last `.` (if any), starting from the second character of - // the basename (a leading `.` does not make the whole path component - // a suffix). - let fIdx = string.index(bIdx, offsetBy: 1, limitedBy: string.endIndex) ?? string.startIndex - if let idx = string[fIdx...].lastIndex(of: ".") { - // Unless it's just a `.` at the end, we have found a suffix. - if string.distance(from: idx, to: string.endIndex) > 1 { - let fromIndex = withDot ? idx : string.index(idx, offsetBy: 1) - return String(string.suffix(from: fromIndex)) - } else { - return nil - } - } - // If we get this far, there is no suffix. - return nil -#endif - } - - func appending(component name: String) -> UNIXPath { -#if os(Windows) - var result: PWSTR? - _ = string.withCString(encodedAs: UTF16.self) { root in - name.withCString(encodedAs: UTF16.self) { path in - PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) - } - } - defer { LocalFree(result) } - return PathImpl(string: String(decodingCString: result!, as: UTF16.self)) -#else - assert(!name.contains("/"), "\(name) is invalid path component") - - // Handle pseudo paths. - switch name { - case "", ".": - return self - case "..": - return self.parentDirectory - default: - break - } - - if self == Self.root { - return PathImpl(string: "/" + name) - } else { - return PathImpl(string: string + "/" + name) - } -#endif - } - - func appending(relativePath: UNIXPath) -> UNIXPath { -#if os(Windows) - var result: PWSTR? - _ = string.withCString(encodedAs: UTF16.self) { root in - relativePath.string.withCString(encodedAs: UTF16.self) { path in - PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result) - } - } - defer { LocalFree(result) } - return PathImpl(string: String(decodingCString: result!, as: UTF16.self)) -#else - // Both paths are already normalized. The only case in which we have - // to renormalize their concatenation is if the relative path starts - // with a `..` path component. - var newPathString = string - if self != .root { - newPathString.append("/") - } - - let relativePathString = relativePath.string - newPathString.append(relativePathString) - - // If the relative string starts with `.` or `..`, we need to normalize - // the resulting string. - // FIXME: We can actually optimize that case, since we know that the - // normalization of a relative path can leave `..` path components at - // the beginning of the path only. - if relativePathString.hasPrefix(".") { - if newPathString.hasPrefix("/") { - return PathImpl(normalizingAbsolutePath: newPathString) - } else { - return PathImpl(normalizingRelativePath: newPathString) - } - } else { - return PathImpl(string: newPathString) - } -#endif - } -} - /// Describes the way in which a path is invalid. public enum PathValidationError: Error { - case startsWithTilde(String) case invalidAbsolutePath(String) case invalidRelativePath(String) + case differentRoot(String, String) } extension PathValidationError: CustomStringConvertible { public var description: String { switch self { - case .startsWithTilde(let path): - return "invalid absolute path '\(path)'; absolute path must begin with '/'" case .invalidAbsolutePath(let path): return "invalid absolute path '\(path)'" case .invalidRelativePath(let path): - return "invalid relative path '\(path)'; relative path should not begin with '/' or '~'" + return "invalid relative path '\(path)'" + case .differentRoot(let pathA, let pathB): + return "absolute paths '\(pathA)' and '\(pathB)' have different roots" } } } @@ -865,45 +356,29 @@ extension AbsolutePath { /// /// This method is strictly syntactic and does not access the file system /// in any way. Therefore, it does not take symbolic links into account. - public func relative(to base: AbsolutePath) -> RelativePath { - let result: RelativePath - // Split the two paths into their components. - // FIXME: The is needs to be optimized to avoid unncessary copying. - let pathComps = self.components - let baseComps = base.components - - // It's common for the base to be an ancestor, so try that first. - if pathComps.starts(with: baseComps) { - // Special case, which is a plain path without `..` components. It - // might be an empty path (when self and the base are equal). - let relComps = pathComps.dropFirst(baseComps.count) -#if os(Windows) - result = RelativePath(relComps.joined(separator: "\\")) -#else - result = RelativePath(relComps.joined(separator: "/")) -#endif - } else { - // General case, in which we might well need `..` components to go - // "up" before we can go "down" the directory tree. - var newPathComps = ArraySlice(pathComps) - var newBaseComps = ArraySlice(baseComps) - while newPathComps.prefix(1) == newBaseComps.prefix(1) { - // First component matches, so drop it. - newPathComps = newPathComps.dropFirst() - newBaseComps = newBaseComps.dropFirst() - } - // Now construct a path consisting of as many `..`s as are in the - // `newBaseComps` followed by what remains in `newPathComps`. - var relComps = Array(repeating: "..", count: newBaseComps.count) - relComps.append(contentsOf: newPathComps) + public func relative(to base: AbsolutePath) throws -> RelativePath { + var relFilePath = FilePath() + var filepath = filepath #if os(Windows) - result = RelativePath(relComps.joined(separator: "\\")) -#else - result = RelativePath(relComps.joined(separator: "/")) -#endif + /// TODO: DOS relative path may change the root. + if root != base.root { + throw PathValidationError.differentRoot(pathString, base.pathString) } - assert(base.appending(result) == self) - return result +#endif + filepath.root = base.filepath.root + + let commonAncestor = AbsolutePath(filepath).lowestCommonAncestor(with: base)! + let walkbackDepth: Int = { + var baseFilepath = base.filepath + precondition(baseFilepath.removePrefix(commonAncestor.filepath)) + return baseFilepath.components.count + }() + precondition(filepath.removePrefix(commonAncestor.filepath)) + + relFilePath.append(Array(repeating: FilePath.Component(".."), count: walkbackDepth)) + relFilePath.push(filepath) + + return RelativePath(relFilePath) } /// Returns true if the path contains the given path. @@ -911,7 +386,7 @@ extension AbsolutePath { /// This method is strictly syntactic and does not access the file system /// in any way. public func contains(_ other: AbsolutePath) -> Bool { - return self.components.starts(with: other.components) + return filepath.starts(with: other.filepath) } } @@ -922,31 +397,35 @@ extension PathValidationError: CustomNSError { } } -// FIXME: We should consider whether to merge the two `normalize()` functions. -// The argument for doing so is that some of the code is repeated; the argument -// against doing so is that some of the details are different, and since any -// given path is either absolute or relative, it's wasteful to keep checking -// for whether it's relative or absolute. Possibly we can do both by clever -// use of generics that abstract away the differences. +extension FilePath { + static var _root: FilePath { +#if os(Windows) + return FilePath("\\") +#else + return FilePath("/") +#endif + } + + init(validatingAbsolutePath path: String) throws { + self.init(path) + guard self.isAbsolute else { + throw PathValidationError.invalidAbsolutePath(path) + } + } -/// Fast check for if a string might need normalization. -/// -/// This assumes that paths containing dotfiles are rare: -private func mayNeedNormalization(absolute string: String) -> Bool { - var last = UInt8(ascii: "0") - for c in string.utf8 { - switch c { - case UInt8(ascii: "/") where last == UInt8(ascii: "/"): - return true - case UInt8(ascii: ".") where last == UInt8(ascii: "/"): - return true - default: - break + init(validatingRelativePath path: String) throws { + self.init(path) + guard self.isRelative else { + throw PathValidationError.invalidRelativePath(path) } - last = c +#if !os(Windows) + guard self.components.first?.string != "~" else { + throw PathValidationError.invalidRelativePath(path) + } +#endif } - if last == UInt8(ascii: "/") { - return true + + var isRoot: Bool { + root != nil && components.isEmpty } - return false } diff --git a/Sources/TSCBasic/PathShims.swift b/Sources/TSCBasic/PathShims.swift index bae0210d..4fad5121 100644 --- a/Sources/TSCBasic/PathShims.swift +++ b/Sources/TSCBasic/PathShims.swift @@ -19,6 +19,7 @@ import TSCLibc import Foundation +import SystemPackage /// Returns the "real path" corresponding to `path` by resolving any symbolic links. public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath { @@ -63,7 +64,7 @@ public func makeDirectories(_ path: AbsolutePath) throws { /// be a relative path, otherwise it will be absolute. @available(*, deprecated, renamed: "localFileSystem.createSymbolicLink") public func createSymlink(_ path: AbsolutePath, pointingAt dest: AbsolutePath, relative: Bool = true) throws { - let destString = relative ? dest.relative(to: path.parentDirectory).pathString : dest.pathString + let destString = relative ? try dest.relative(to: path.parentDirectory).pathString : dest.pathString try FileManager.default.createSymbolicLink(atPath: path.pathString, withDestinationPath: destString) } @@ -177,7 +178,7 @@ extension AbsolutePath { if self == dir { return "." } else if self.pathString.hasPrefix(dir.pathString + "/") { - return "./" + self.relative(to: dir).pathString + return try! "./" + self.relative(to: dir).pathString } else { return self.pathString } diff --git a/Tests/TSCBasicTests/PathShimTests.swift b/Tests/TSCBasicTests/PathShimTests.swift index 9d79535a..f7a2dbf7 100644 --- a/Tests/TSCBasicTests/PathShimTests.swift +++ b/Tests/TSCBasicTests/PathShimTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -54,9 +54,9 @@ class WalkTests : XCTestCase { expected.remove(at: i) } #if os(Android) - XCTAssertEqual(3, x.components.count) - #else XCTAssertEqual(2, x.components.count) + #else + XCTAssertEqual(1, x.components.count) #endif } XCTAssertEqual(expected.count, 0) diff --git a/Tests/TSCBasicTests/PathTests.swift b/Tests/TSCBasicTests/PathTests.swift index ea8c191f..34f5d79d 100644 --- a/Tests/TSCBasicTests/PathTests.swift +++ b/Tests/TSCBasicTests/PathTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -229,8 +229,6 @@ class PathTests: XCTestCase { XCTAssertEqual(AbsolutePath("/").appending(components: "a", "b").pathString, "/a/b") XCTAssertEqual(AbsolutePath("/a").appending(components: "b", "c").pathString, "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "", "c").pathString, "/a/b/c/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "").pathString, "/a/b/c") XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: ".").pathString, "/a/b/c") XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..").pathString, "/a/b") XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString, "/a/b/d") @@ -243,14 +241,14 @@ class PathTests: XCTestCase { } func testPathComponents() { - XCTAssertEqual(AbsolutePath("/").components, ["/"]) - XCTAssertEqual(AbsolutePath("/.").components, ["/"]) - XCTAssertEqual(AbsolutePath("/..").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar").components, ["/", "bar"]) - XCTAssertEqual(AbsolutePath("/foo/bar/..").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b/").components, ["/", "yabba", "a", "b"]) + XCTAssertEqual(AbsolutePath("/").components, []) + XCTAssertEqual(AbsolutePath("/.").components, []) + XCTAssertEqual(AbsolutePath("/..").components, []) + XCTAssertEqual(AbsolutePath("/bar").components, ["bar"]) + XCTAssertEqual(AbsolutePath("/foo/bar/..").components, ["foo"]) + XCTAssertEqual(AbsolutePath("/bar/../foo").components, ["foo"]) + XCTAssertEqual(AbsolutePath("/bar/../foo/..//").components, []) + XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b/").components, ["yabba", "a", "b"]) XCTAssertEqual(RelativePath("").components, ["."]) XCTAssertEqual(RelativePath(".").components, ["."]) @@ -271,13 +269,13 @@ class PathTests: XCTestCase { } func testRelativePathFromAbsolutePaths() { - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")), RelativePath("../../..")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")), RelativePath("c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")), RelativePath("d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")), RelativePath("../../b/c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")), RelativePath("../../../a/b/c/d")); + XCTAssertEqual(try! AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); + XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); + XCTAssertEqual(try! AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")), RelativePath("../../..")); + XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")), RelativePath("c/d")); + XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")), RelativePath("d")); + XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")), RelativePath("../../b/c/d")); + XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")), RelativePath("../../../a/b/c/d")); } func testComparison() { @@ -302,7 +300,7 @@ class PathTests: XCTestCase { XCTAssertNoThrow(try AbsolutePath(validating: "/a/b/c/d")) XCTAssertThrowsError(try AbsolutePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid absolute path '~/a/b/d'; absolute path must begin with '/'") + XCTAssertEqual("\(error)", "invalid absolute path '~/a/b/d'") } XCTAssertThrowsError(try AbsolutePath(validating: "a/b/d")) { error in @@ -314,11 +312,11 @@ class PathTests: XCTestCase { XCTAssertNoThrow(try RelativePath(validating: "a/b/c/d")) XCTAssertThrowsError(try RelativePath(validating: "/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'; relative path should not begin with '/' or '~'") + XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'") } XCTAssertThrowsError(try RelativePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '~/a/b/d'; relative path should not begin with '/' or '~'") + XCTAssertEqual("\(error)", "invalid relative path '~/a/b/d'") } } From b127725996b9ac9db69ffac65fbd2bf4b8db1b9e Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 11:37:11 +0800 Subject: [PATCH 02/15] Drop Pathcch dependency --- Package.swift | 13 ------------- Sources/TSCBasic/CMakeLists.txt | 2 -- 2 files changed, 15 deletions(-) diff --git a/Package.swift b/Package.swift index 80a5360e..acd28441 100644 --- a/Package.swift +++ b/Package.swift @@ -77,16 +77,3 @@ let package = Package( dependencies: ["TSCUtility", "TSCTestSupport"]), ] ) - -// FIXME: conditionalise these flags since SwiftPM 5.3 and earlier will crash -// for platforms they don't know about. -#if os(Windows) - if let TSCBasic = package.targets.first(where: { $0.name == "TSCBasic" }) { - TSCBasic.cxxSettings = [ - .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), - ] - TSCBasic.linkerSettings = [ - .linkedLibrary("Pathcch", .when(platforms: [.windows])), - ] - } -#endif diff --git a/Sources/TSCBasic/CMakeLists.txt b/Sources/TSCBasic/CMakeLists.txt index c5efd819..2c038507 100644 --- a/Sources/TSCBasic/CMakeLists.txt +++ b/Sources/TSCBasic/CMakeLists.txt @@ -66,8 +66,6 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) Foundation) endif() endif() -target_link_libraries(TSCBasic PRIVATE - $<$:Pathcch>) # NOTE(compnerd) workaround for CMake not setting up include flags yet set_target_properties(TSCBasic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) From d8888c60e43f97d2a2ea54b6f95d537c2d4eebc0 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 11:39:45 +0800 Subject: [PATCH 03/15] Fix style --- Sources/TSCBasic/Path.swift | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 6c556bbf..8779f0a9 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -15,16 +15,16 @@ import var Foundation.NSLocalizedDescriptionKey public protocol Path: Hashable, Codable, CustomStringConvertible { /// Underlying type, based on SwiftSystem. var filepath: FilePath { get } - + /// Public initializer from FilePath. init(_ filepath: FilePath) - + /// Public initializer from String. init(_ string: String) - + /// Convenience initializer that verifies that the path lexically. init(validating path: String) throws - + /// Normalized string representation (the normalization rules are described /// in the documentation of the initializer). This string is never empty. var pathString: String { get } @@ -38,27 +38,27 @@ public protocol Path: Hashable, Codable, CustomStringConvertible { /// Last path component (including the suffix, if any). var basename: String { get } - + /// Returns the basename without the extension. var basenameWithoutExt: String { get } - + /// Extension of the give path's basename. This follow same rules as /// suffix except that it doesn't include leading `.` character. var `extension`: String? { get } - + /// Suffix (including leading `.` character) if any. Note that a basename /// that starts with a `.` character is not considered a suffix, nor is a /// trailing `.` character. var suffix: String? { get } - + /// True if the path is a root directory. var isRoot: Bool { get } - + /// Returns the path with an additional literal component appended. /// /// This method accepts pseudo-path like '.' or '..', but should not contain "/". func appending(component: String) -> Self - + /// Returns the relative path with additional literal components appended. /// /// This method should only be used in cases where the input is guaranteed @@ -66,7 +66,7 @@ public protocol Path: Hashable, Codable, CustomStringConvertible { /// separator, or be a pseudo-path like '.' or '..'). func appending(components names: [String]) -> Self func appending(components names: String...) -> Self - + /// Returns an array of strings that make up the path components of the /// path. This is the same sequence of strings as the basenames of each /// successive path component. @@ -83,11 +83,11 @@ extension Path { } return filepath.string } - + public var root: String? { return filepath.root?.string } - + public var dirname: String { let dirname = filepath.removingLastComponent().string if dirname.isEmpty { @@ -95,15 +95,15 @@ extension Path { } return dirname } - + public var basename: String { return filepath.lastComponent?.string ?? root ?? "." } - + public var basenameWithoutExt: String { return filepath.lastComponent?.stem ?? root ?? "." } - + public var `extension`: String? { guard let ext = filepath.extension, !ext.isEmpty else { @@ -111,7 +111,7 @@ extension Path { } return filepath.extension } - + public var suffix: String? { if let ext = `extension` { return ".\(ext)" @@ -123,7 +123,7 @@ extension Path { public var isRoot: Bool { return filepath.isRoot } - + public func appending(component: String) -> Self { return Self(filepath.appending( FilePath.Component(stringLiteral: component))) @@ -137,7 +137,7 @@ extension Path { public func appending(components names: String...) -> Self { appending(components: names) } - + public var components: [String] { var components = filepath.components.map(\.string) if filepath.isRelative && components.isEmpty { @@ -145,7 +145,7 @@ extension Path { } return components } - + public var description: String { return pathString } @@ -424,7 +424,7 @@ extension FilePath { } #endif } - + var isRoot: Bool { root != nil && components.isEmpty } From 49264e3daca6ab3b4b89ff289615392d9d98df3b Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 11:42:39 +0800 Subject: [PATCH 04/15] Clarify the change of `Path.components` --- Sources/TSCBasic/Path.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 8779f0a9..667e5fec 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -69,10 +69,10 @@ public protocol Path: Hashable, Codable, CustomStringConvertible { /// Returns an array of strings that make up the path components of the /// path. This is the same sequence of strings as the basenames of each - /// successive path component. - /// - /// An empty path has a single path + /// successive path component. An empty path has a single path /// component: the `.` string. + /// + /// NOTE: Path components no longer include the root. Use `root` instead. var components: [String] { get } } From 76ae479aaa7ec477172c2dec60f592473c11db5a Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 12:22:30 +0800 Subject: [PATCH 05/15] Deprecate static method AbsolutePath.root --- Sources/TSCBasic/FileSystem.swift | 6 +++--- Sources/TSCBasic/Path.swift | 9 ++++++++- Sources/TSCUtility/Platform.swift | 4 ++-- .../SynchronizedQueuePerfTests.swift | 7 +++---- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index 05e9e119..b724adab 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -981,11 +981,11 @@ public class RerootedFileSystemView: FileSystem { /// Adjust the input path for the underlying file system. private func formUnderlyingPath(_ path: AbsolutePath) -> AbsolutePath { - if path == AbsolutePath.root { + if path.isRoot { return root } else { // FIXME: Optimize? - return root.appending(RelativePath(String(path.pathString.dropFirst(1)))) + return root.appending(RelativePath(path.filepath.removingRoot())) } } diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 667e5fec..7d450cc4 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -268,7 +268,14 @@ public struct AbsolutePath: Path { /// Root directory (whose string representation is just a path separator). /// /// FIXME: root is not a static value, we'd better remove this property. - public static let root = AbsolutePath(FilePath._root) + @available(*, deprecated, message: "root is not a static value, use the instance property instead") + public static var root: AbsolutePath { + if let rootPath = localFileSystem.currentWorkingDirectory?.root { + return AbsolutePath(rootPath) + } else { + return AbsolutePath(FilePath._root) + } + } } /// Represents a relative file system path. A relative path never starts with diff --git a/Sources/TSCUtility/Platform.swift b/Sources/TSCUtility/Platform.swift index e62e6511..9adb2ac4 100644 --- a/Sources/TSCUtility/Platform.swift +++ b/Sources/TSCUtility/Platform.swift @@ -105,8 +105,8 @@ public enum Platform: Equatable { defer { tmp.deallocate() } guard confstr(name, tmp.baseAddress, len) == len else { return nil } let value = String(cString: tmp.baseAddress!) - guard value.hasSuffix(AbsolutePath.root.pathString) else { return nil } - return resolveSymlinks(AbsolutePath(value)) + guard let path = try? AbsolutePath(validating: value) else { return nil } + return resolveSymlinks(path) } #endif } diff --git a/Tests/TSCBasicPerformanceTests/SynchronizedQueuePerfTests.swift b/Tests/TSCBasicPerformanceTests/SynchronizedQueuePerfTests.swift index a68a5281..83c57a37 100644 --- a/Tests/TSCBasicPerformanceTests/SynchronizedQueuePerfTests.swift +++ b/Tests/TSCBasicPerformanceTests/SynchronizedQueuePerfTests.swift @@ -14,7 +14,7 @@ import TSCBasic import TSCTestSupport class SynchronizedQueuePerfTests: XCTestCasePerf { - + // Mock the UnitTest struct in SwiftPM/SwiftTestTool.swift struct Item { let productPath: AbsolutePath @@ -28,7 +28,6 @@ class SynchronizedQueuePerfTests: XCTestCasePerf { } } - func testEnqueueDequeue_10000() { let queue = SynchronizedQueue() let test = Item(productPath: AbsolutePath.root, name: "TestName", testCase: "TestCaseName") @@ -42,7 +41,7 @@ class SynchronizedQueuePerfTests: XCTestCasePerf { } } } - + func testEnqueueDequeue_1000() { let queue = SynchronizedQueue() let test = Item(productPath: AbsolutePath.root, name: "TestName", testCase: "TestCaseName") @@ -56,7 +55,7 @@ class SynchronizedQueuePerfTests: XCTestCasePerf { } } } - + func testEnqueueDequeue_100() { let queue = SynchronizedQueue() let test = Item(productPath: AbsolutePath.root, name: "TestName", testCase: "TestCaseName") From 55565baaee66a4ee73680d455a5f878e05f568b6 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 12:49:16 +0800 Subject: [PATCH 06/15] Minor style changes --- Sources/TSCBasic/Path.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 7d450cc4..0188a1b5 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -76,6 +76,7 @@ public protocol Path: Hashable, Codable, CustomStringConvertible { var components: [String] { get } } +/// Default implementations of some protocol stubs. extension Path { public var pathString: String { if filepath.string.isEmpty { @@ -145,7 +146,10 @@ extension Path { } return components } +} +/// Default implementation of `CustomStringConvertible`. +extension Path { public var description: String { return pathString } @@ -156,6 +160,7 @@ extension Path { } } +/// Default implementation of `Codable`. extension Path { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -265,9 +270,7 @@ public struct AbsolutePath: Path { return AbsolutePath(filepath) } - /// Root directory (whose string representation is just a path separator). - /// - /// FIXME: root is not a static value, we'd better remove this property. + /// The root directory. It is always `/` on UNIX, but may vary on Windows. @available(*, deprecated, message: "root is not a static value, use the instance property instead") public static var root: AbsolutePath { if let rootPath = localFileSystem.currentWorkingDirectory?.root { From 39704db3337ffb0e331df8d271688932e9e9df39 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 21:22:30 +0800 Subject: [PATCH 07/15] Minor update --- Sources/TSCBasic/Path.swift | 4 ++-- Sources/TSCBasic/PathShims.swift | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 0188a1b5..c4f60875 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -114,8 +114,8 @@ extension Path { } public var suffix: String? { - if let ext = `extension` { - return ".\(ext)" + if let ext = self.extension { + return "." + ext } else { return nil } diff --git a/Sources/TSCBasic/PathShims.swift b/Sources/TSCBasic/PathShims.swift index 4fad5121..e50c26f3 100644 --- a/Sources/TSCBasic/PathShims.swift +++ b/Sources/TSCBasic/PathShims.swift @@ -19,7 +19,6 @@ import TSCLibc import Foundation -import SystemPackage /// Returns the "real path" corresponding to `path` by resolving any symbolic links. public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath { @@ -169,18 +168,16 @@ extension AbsolutePath { /// Returns a path suitable for display to the user (if possible, it is made /// to be relative to the current working directory). public func prettyPath(cwd: AbsolutePath? = localFileSystem.currentWorkingDirectory) -> String { - guard let dir = cwd else { - // No current directory, display as is. + guard let dir = cwd, + let rel = try? relative(to: dir) else { + // Cannot create relative path, display as is. return self.pathString } - // FIXME: Instead of string prefix comparison we should add a proper API - // to AbsolutePath to determine ancestry. - if self == dir { - return "." - } else if self.pathString.hasPrefix(dir.pathString + "/") { - return try! "./" + self.relative(to: dir).pathString + if let first = rel.components.first, + first != ".." { + return "./" + rel.pathString } else { - return self.pathString + return rel.pathString } } } From 44f7f256d3876327dcce70db36c72e267ed9c259 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Sun, 6 Jun 2021 21:32:21 +0800 Subject: [PATCH 08/15] Fix build --- Package.swift | 10 ++++++++++ Sources/TSCBasic/CMakeLists.txt | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index acd28441..63c59e33 100644 --- a/Package.swift +++ b/Package.swift @@ -77,3 +77,13 @@ let package = Package( dependencies: ["TSCUtility", "TSCTestSupport"]), ] ) + +// FIXME: conditionalise these flags since SwiftPM 5.3 and earlier will crash +// for platforms they don't know about. +#if os(Windows) + if let TSCBasic = package.targets.first(where: { $0.name == "TSCBasic" }) { + TSCBasic.cxxSettings = [ + .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), + ] + } +#endif diff --git a/Sources/TSCBasic/CMakeLists.txt b/Sources/TSCBasic/CMakeLists.txt index 2c038507..f853f868 100644 --- a/Sources/TSCBasic/CMakeLists.txt +++ b/Sources/TSCBasic/CMakeLists.txt @@ -57,9 +57,10 @@ target_compile_options(TSCBasic PUBLIC # Ignore secure function warnings on Windows. "$<$:SHELL:-Xcc -D_CRT_SECURE_NO_WARNINGS>") target_link_libraries(TSCBasic PUBLIC - TSCLibc) + TSCLibc + SwiftSystem) target_link_libraries(TSCBasic PRIVATE - TSCclibc) + TSCclibc) if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) if(Foundation_FOUND) target_link_libraries(TSCBasic PUBLIC From 2727fbf92948d6d3fdbc77312d0a7ddc23f5c7c5 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Mon, 7 Jun 2021 00:58:42 +0800 Subject: [PATCH 09/15] Minor fix --- Sources/TSCBasic/PathShims.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/TSCBasic/PathShims.swift b/Sources/TSCBasic/PathShims.swift index e50c26f3..a5e17e8f 100644 --- a/Sources/TSCBasic/PathShims.swift +++ b/Sources/TSCBasic/PathShims.swift @@ -175,7 +175,11 @@ extension AbsolutePath { } if let first = rel.components.first, first != ".." { +#if os(Windows) + return ".\\" + rel.pathString +#else return "./" + rel.pathString +#endif } else { return rel.pathString } From df853dc148602cf3d6c998a57a61c19483130c6d Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 8 Jun 2021 05:19:05 +0800 Subject: [PATCH 10/15] Rename Path to FileSystemPath --- Package.resolved | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index ebd3ef7f..00000000 --- a/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "swift-system", - "repositoryURL": "https://github.com/apple/swift-system.git", - "state": { - "branch": null, - "revision": "2bc160bfe34d843ae5ff47168080add24dfd7eac", - "version": "0.0.2" - } - } - ] - }, - "version": 1 -} From d27bd63567ad25e582362d3bde5054870366114f Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 8 Jun 2021 15:00:30 +0800 Subject: [PATCH 11/15] Improve stability on Windows --- Sources/TSCBasic/Path.swift | 30 ++++++++++++++------ Tests/TSCBasicTests/TemporaryFileTests.swift | 2 ++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index c4f60875..7c7512b8 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -276,8 +276,28 @@ public struct AbsolutePath: Path { if let rootPath = localFileSystem.currentWorkingDirectory?.root { return AbsolutePath(rootPath) } else { - return AbsolutePath(FilePath._root) +#if !os(Windows) + return AbsolutePath("/") +#else + if let drive = ProcessEnv.vars["SystemDrive"] ?? ProcessEnv.vars["HomeDrive"] { + return AbsolutePath(drive + "\\") + } else { + fatalError("cannot determine the drive") + } +#endif + } + } + + public static func withPOSIX(path: String) -> AbsolutePath { +#if os(Windows) + var filepath = FilePath(path) + if !filepath.isAbsolute { + filepath.root = root.filepath.root } + return AbsolutePath(filepath) +#else + return AbsolutePath(path) +#endif } } @@ -408,14 +428,6 @@ extension PathValidationError: CustomNSError { } extension FilePath { - static var _root: FilePath { -#if os(Windows) - return FilePath("\\") -#else - return FilePath("/") -#endif - } - init(validatingAbsolutePath path: String) throws { self.init(path) guard self.isAbsolute else { diff --git a/Tests/TSCBasicTests/TemporaryFileTests.swift b/Tests/TSCBasicTests/TemporaryFileTests.swift index 18cbf526..407b9d98 100644 --- a/Tests/TSCBasicTests/TemporaryFileTests.swift +++ b/Tests/TSCBasicTests/TemporaryFileTests.swift @@ -137,6 +137,7 @@ class TemporaryFileTests: XCTestCase { XCTAssertFalse(localFileSystem.isDirectory(pathTwo)) } +#if !os(Windows) /// Check that the temporary file doesn't leak file descriptors. func testLeaks() throws { // We check this by testing that we get back the same FD after a @@ -150,4 +151,5 @@ class TemporaryFileTests: XCTestCase { let endFD = try Int(withTemporaryFile { return $0.fileHandle.fileDescriptor }) XCTAssertEqual(initialFD, endFD) } +#endif } From 08ece27ff88d0cbb8015f92665ebaf3556d0bc1c Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 8 Jun 2021 15:37:02 +0800 Subject: [PATCH 12/15] Fix some tests on Windows --- Sources/TSCBasic/FileSystem.swift | 4 +- Sources/TSCBasic/Path.swift | 1 + .../TSCTestSupport/FileSystemExtensions.swift | 5 +- Sources/TSCUtility/Archiver.swift | 2 +- Tests/TSCBasicTests/FileSystemTests.swift | 101 ++++----- Tests/TSCBasicTests/PathShimTests.swift | 14 +- Tests/TSCBasicTests/PathTests.swift | 205 +++++++++--------- Tests/TSCBasicTests/miscTests.swift | 4 +- Tests/TSCUtilityTests/ArchiverTests.swift | 12 +- .../PkgConfigParserTests.swift | 10 +- .../SimplePersistenceTests.swift | 10 +- 11 files changed, 187 insertions(+), 181 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index b724adab..0cb9e6a1 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -706,7 +706,7 @@ public class InMemoryFileSystem: FileSystem { /// Virtualized current working directory. public var currentWorkingDirectory: AbsolutePath? { - return AbsolutePath("/") + return AbsolutePath.withPOSIX(path: "/") } public func changeCurrentWorkingDirectory(to path: AbsolutePath) throws { @@ -715,7 +715,7 @@ public class InMemoryFileSystem: FileSystem { public var homeDirectory: AbsolutePath { // FIXME: Maybe we should allow setting this when creating the fs. - return AbsolutePath("/home/user") + return AbsolutePath.withPOSIX(path: "/home/user") } public var cachesDirectory: AbsolutePath? { diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 7c7512b8..ce1cff1b 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -291,6 +291,7 @@ public struct AbsolutePath: Path { public static func withPOSIX(path: String) -> AbsolutePath { #if os(Windows) var filepath = FilePath(path) + precondition(filepath.root != nil) if !filepath.isAbsolute { filepath.root = root.filepath.root } diff --git a/Sources/TSCTestSupport/FileSystemExtensions.swift b/Sources/TSCTestSupport/FileSystemExtensions.swift index 94fbef99..dd162ccc 100644 --- a/Sources/TSCTestSupport/FileSystemExtensions.swift +++ b/Sources/TSCTestSupport/FileSystemExtensions.swift @@ -25,7 +25,7 @@ extension InMemoryFileSystem { self.init() for (path, contents) in files { - let path = AbsolutePath(path) + let path = AbsolutePath.withPOSIX(path: path) try! createDirectory(path.parentDirectory, recursive: true) try! writeFileContents(path, bytes: contents) } @@ -52,7 +52,8 @@ extension FileSystem { do { try createDirectory(root, recursive: true) for path in files { - let path = root.appending(RelativePath(String(path.dropFirst()))) + let components = AbsolutePath.withPOSIX(path: path).components + let path = root.appending(components: components) try createDirectory(path.parentDirectory, recursive: true) try writeFileContents(path, bytes: "") } diff --git a/Sources/TSCUtility/Archiver.swift b/Sources/TSCUtility/Archiver.swift index 5d0f7db0..dd3c9c86 100644 --- a/Sources/TSCUtility/Archiver.swift +++ b/Sources/TSCUtility/Archiver.swift @@ -62,7 +62,7 @@ public struct ZipArchiver: Archiver { DispatchQueue.global(qos: .userInitiated).async { do { - let result = try Process.popen(args: "unzip", archivePath.pathString, "-d", destinationPath.pathString) + let result = try Process.popen(args: "unzip\(executableFileSuffix)", archivePath.pathString, "-d", destinationPath.pathString) guard result.exitStatus == .terminated(code: 0) else { throw try StringError(result.utf8stderrOutput()) } diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index 0c548674..00f04f83 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -17,23 +17,26 @@ import TSCLibc class FileSystemTests: XCTestCase { // MARK: LocalFS Tests - func testLocalBasics() throws { let fs = TSCBasic.localFileSystem +#if os(Windows) + XCTSkip("FIXME: withTemporaryDirectory(removeTreeOnDeinit: true) will throw on Windows") + return +#endif try! withTemporaryFile { file in try! withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in // exists() - XCTAssert(fs.exists(AbsolutePath("/"))) - XCTAssert(!fs.exists(AbsolutePath("/does-not-exist"))) + XCTAssert(fs.exists(AbsolutePath.withPOSIX(path: "/"))) + XCTAssert(!fs.exists(AbsolutePath.withPOSIX(path: "/does-not-exist"))) // isFile() XCTAssertTrue(fs.exists(file.path)) XCTAssertTrue(fs.isFile(file.path)) XCTAssertEqual(try fs.getFileInfo(file.path).fileType, .typeRegular) XCTAssertFalse(fs.isDirectory(file.path)) - XCTAssertFalse(fs.isFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(fs.isSymlink(AbsolutePath("/does-not-exist"))) - XCTAssertThrowsError(try fs.getFileInfo(AbsolutePath("/does-not-exist"))) + XCTAssertFalse(fs.isFile(AbsolutePath.withPOSIX(path: "/does-not-exist"))) + XCTAssertFalse(fs.isSymlink(AbsolutePath.withPOSIX(path: "/does-not-exist"))) + XCTAssertThrowsError(try fs.getFileInfo(AbsolutePath.withPOSIX(path: "/does-not-exist"))) // isSymlink() let sym = tempDirPath.appending(component: "hello") @@ -46,7 +49,7 @@ class FileSystemTests: XCTestCase { // isExecutableFile let executable = tempDirPath.appending(component: "exec-foo") let executableSym = tempDirPath.appending(component: "exec-sym") - try! fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) + try fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) let stream = BufferedOutputByteStream() stream <<< """ #!/bin/sh @@ -61,16 +64,16 @@ class FileSystemTests: XCTestCase { XCTAssertTrue(fs.isSymlink(executableSym)) XCTAssertFalse(fs.isExecutableFile(sym)) XCTAssertFalse(fs.isExecutableFile(file.path)) - XCTAssertFalse(fs.isExecutableFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(fs.isExecutableFile(AbsolutePath("/"))) + XCTAssertFalse(fs.isExecutableFile(AbsolutePath.withPOSIX(path: "/does-not-exist"))) + XCTAssertFalse(fs.isExecutableFile(AbsolutePath.withPOSIX(path: "/"))) // isDirectory() - XCTAssert(fs.isDirectory(AbsolutePath("/"))) - XCTAssert(!fs.isDirectory(AbsolutePath("/does-not-exist"))) + XCTAssert(fs.isDirectory(AbsolutePath.withPOSIX(path: "/"))) + XCTAssert(!fs.isDirectory(AbsolutePath.withPOSIX(path: "/does-not-exist"))) // getDirectoryContents() do { - _ = try fs.getDirectoryContents(AbsolutePath("/does-not-exist")) + _ = try fs.getDirectoryContents(AbsolutePath.withPOSIX(path: "/does-not-exist")) XCTFail("Unexpected success") } catch { XCTAssertEqual(error.localizedDescription, "The folder “does-not-exist” doesn’t exist.") @@ -219,7 +222,7 @@ class FileSystemTests: XCTestCase { XCTAssertEqual(data, ByteString(testData)) // Atomic writes - let inMemoryFilePath = AbsolutePath("/file.text") + let inMemoryFilePath = AbsolutePath.withPOSIX(path: "/file.text") XCTAssertNoThrow(try TSCBasic.InMemoryFileSystem(files: [:]).writeFileContents(inMemoryFilePath, bytes: ByteString(testData), atomically: true)) XCTAssertNoThrow(try TSCBasic.InMemoryFileSystem(files: [:]).writeFileContents(inMemoryFilePath, bytes: ByteString(testData), atomically: false)) // Local file system does support atomic writes, so it doesn't throw. @@ -255,9 +258,9 @@ class FileSystemTests: XCTestCase { // Check read/write against root. #if os(Android) - let root = AbsolutePath("/system/") + let root = AbsolutePath.withPOSIX(path: "/system/") #else - let root = AbsolutePath("/") + let root = AbsolutePath.withPOSIX(path: "/") #endif XCTAssertThrows(FileSystemError(.ioError(code: TSCLibc.EPERM), root)) { _ = try fs.readFileContents(root) @@ -376,7 +379,7 @@ class FileSystemTests: XCTestCase { func testInMemoryBasics() throws { let fs = InMemoryFileSystem() - let doesNotExist = AbsolutePath("/does-not-exist") + let doesNotExist = AbsolutePath.withPOSIX(path: "/does-not-exist") // exists() XCTAssert(!fs.exists(doesNotExist)) @@ -396,22 +399,22 @@ class FileSystemTests: XCTestCase { } // createDirectory() - XCTAssert(!fs.isDirectory(AbsolutePath("/new-dir"))) - try fs.createDirectory(AbsolutePath("/new-dir/subdir"), recursive: true) - XCTAssert(fs.isDirectory(AbsolutePath("/new-dir"))) - XCTAssert(fs.isDirectory(AbsolutePath("/new-dir/subdir"))) - XCTAssertEqual(try fs.getDirectoryContents(AbsolutePath("/")), ["new-dir"]) - XCTAssertEqual(try fs.getDirectoryContents(AbsolutePath("/new-dir")), ["subdir"]) + XCTAssert(!fs.isDirectory(AbsolutePath.withPOSIX(path: "/new-dir"))) + try fs.createDirectory(AbsolutePath.withPOSIX(path: "/new-dir/subdir"), recursive: true) + XCTAssert(fs.isDirectory(AbsolutePath.withPOSIX(path: "/new-dir"))) + XCTAssert(fs.isDirectory(AbsolutePath.withPOSIX(path: "/new-dir/subdir"))) + XCTAssertEqual(try fs.getDirectoryContents(AbsolutePath.withPOSIX(path: "/")), ["new-dir"]) + XCTAssertEqual(try fs.getDirectoryContents(AbsolutePath.withPOSIX(path: "/new-dir")), ["subdir"]) } func testInMemoryCreateDirectory() { let fs = InMemoryFileSystem() // Make sure root entry isn't created. - try! fs.createDirectory(AbsolutePath("/"), recursive: true) + try! fs.createDirectory(AbsolutePath.withPOSIX(path: "/"), recursive: true) let rootContents = try! fs.getDirectoryContents(.root) XCTAssertEqual(rootContents, []) - let subdir = AbsolutePath("/new-dir/subdir") + let subdir = AbsolutePath.withPOSIX(path: "/new-dir/subdir") try! fs.createDirectory(subdir, recursive: true) XCTAssert(fs.isDirectory(subdir)) @@ -426,7 +429,7 @@ class FileSystemTests: XCTestCase { XCTAssert(fs.isDirectory(subsubdir)) // Check non-recursive failing subdir case. - let veryNewDir = AbsolutePath("/very-new-dir") + let veryNewDir = AbsolutePath.withPOSIX(path: "/very-new-dir") let newsubdir = veryNewDir.appending(component: "subdir") XCTAssert(!fs.isDirectory(newsubdir)) XCTAssertThrows(FileSystemError(.noEntry, veryNewDir)) { @@ -435,7 +438,7 @@ class FileSystemTests: XCTestCase { XCTAssert(!fs.isDirectory(newsubdir)) // Check directory creation over a file. - let filePath = AbsolutePath("/mach_kernel") + let filePath = AbsolutePath.withPOSIX(path: "/mach_kernel") try! fs.writeFileContents(filePath, bytes: [0xCD, 0x0D]) XCTAssert(fs.exists(filePath) && !fs.isDirectory(filePath)) XCTAssertThrows(FileSystemError(.notDirectory, filePath)) { @@ -480,10 +483,10 @@ class FileSystemTests: XCTestCase { func testInMemoryReadWriteFile() { let fs = InMemoryFileSystem() - try! fs.createDirectory(AbsolutePath("/new-dir/subdir"), recursive: true) + try! fs.createDirectory(AbsolutePath.withPOSIX(path: "/new-dir/subdir"), recursive: true) // Check read/write of a simple file. - let filePath = AbsolutePath("/new-dir/subdir").appending(component: "new-file.txt") + let filePath = AbsolutePath.withPOSIX(path: "/new-dir/subdir").appending(component: "new-file.txt") XCTAssert(!fs.exists(filePath)) XCTAssertFalse(fs.isFile(filePath)) try! fs.writeFileContents(filePath, bytes: "Hello, world!") @@ -507,7 +510,7 @@ class FileSystemTests: XCTestCase { XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, new world!") // Check read/write against root. - let root = AbsolutePath("/") + let root = AbsolutePath.withPOSIX(path: "/") XCTAssertThrows(FileSystemError(.isDirectory, root)) { _ = try fs.readFileContents(root) } @@ -528,7 +531,7 @@ class FileSystemTests: XCTestCase { XCTAssert(fs.exists(filePath)) // Check read/write into a missing directory. - let missingParent = AbsolutePath("/does/not") + let missingParent = AbsolutePath.withPOSIX(path: "/does/not") let missingFile = missingParent.appending(component: "exist") XCTAssertThrows(FileSystemError(.noEntry, missingFile)) { _ = try fs.readFileContents(missingFile) @@ -541,8 +544,8 @@ class FileSystemTests: XCTestCase { func testInMemoryFsCopy() throws { let fs = InMemoryFileSystem() - try! fs.createDirectory(AbsolutePath("/new-dir/subdir"), recursive: true) - let filePath = AbsolutePath("/new-dir/subdir").appending(component: "new-file.txt") + try! fs.createDirectory(AbsolutePath.withPOSIX(path: "/new-dir/subdir"), recursive: true) + let filePath = AbsolutePath.withPOSIX(path: "/new-dir/subdir").appending(component: "new-file.txt") try! fs.writeFileContents(filePath, bytes: "Hello, world!") XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, world!") @@ -561,7 +564,7 @@ class FileSystemTests: XCTestCase { func testInMemCopyAndMoveItem() throws { let fs = InMemoryFileSystem() - let path = AbsolutePath("/tmp") + let path = AbsolutePath.withPOSIX(path: "/tmp") try fs.createDirectory(path) let source = path.appending(component: "source") let destination = path.appending(component: "destination") @@ -639,33 +642,33 @@ class FileSystemTests: XCTestCase { func testRootedFileSystem() throws { // Create the test file system. let baseFileSystem = InMemoryFileSystem() as FileSystem - try baseFileSystem.createDirectory(AbsolutePath("/base/rootIsHere/subdir"), recursive: true) - try baseFileSystem.writeFileContents(AbsolutePath("/base/rootIsHere/subdir/file"), bytes: "Hello, world!") + try baseFileSystem.createDirectory(AbsolutePath.withPOSIX(path: "/base/rootIsHere/subdir"), recursive: true) + try baseFileSystem.writeFileContents(AbsolutePath.withPOSIX(path: "/base/rootIsHere/subdir/file"), bytes: "Hello, world!") // Create the rooted file system. - let rerootedFileSystem = RerootedFileSystemView(baseFileSystem, rootedAt: AbsolutePath("/base/rootIsHere")) + let rerootedFileSystem = RerootedFileSystemView(baseFileSystem, rootedAt: AbsolutePath.withPOSIX(path: "/base/rootIsHere")) // Check that it has the appropriate view. - XCTAssert(rerootedFileSystem.exists(AbsolutePath("/subdir"))) - XCTAssert(rerootedFileSystem.isDirectory(AbsolutePath("/subdir"))) - XCTAssert(rerootedFileSystem.exists(AbsolutePath("/subdir/file"))) - XCTAssertEqual(try rerootedFileSystem.readFileContents(AbsolutePath("/subdir/file")), "Hello, world!") + XCTAssert(rerootedFileSystem.exists(AbsolutePath.withPOSIX(path: "/subdir"))) + XCTAssert(rerootedFileSystem.isDirectory(AbsolutePath.withPOSIX(path: "/subdir"))) + XCTAssert(rerootedFileSystem.exists(AbsolutePath.withPOSIX(path: "/subdir/file"))) + XCTAssertEqual(try rerootedFileSystem.readFileContents(AbsolutePath.withPOSIX(path: "/subdir/file")), "Hello, world!") // Check that mutations work appropriately. - XCTAssert(!baseFileSystem.exists(AbsolutePath("/base/rootIsHere/subdir2"))) - try rerootedFileSystem.createDirectory(AbsolutePath("/subdir2")) - XCTAssert(baseFileSystem.isDirectory(AbsolutePath("/base/rootIsHere/subdir2"))) + XCTAssert(!baseFileSystem.exists(AbsolutePath.withPOSIX(path: "/base/rootIsHere/subdir2"))) + try rerootedFileSystem.createDirectory(AbsolutePath.withPOSIX(path: "/subdir2")) + XCTAssert(baseFileSystem.isDirectory(AbsolutePath.withPOSIX(path: "/base/rootIsHere/subdir2"))) } func testRootedCreateSymlink() throws { // Create the test file system. let baseFileSystem = InMemoryFileSystem() as FileSystem - try baseFileSystem.createDirectory(AbsolutePath("/base/rootIsHere/subdir"), recursive: true) + try baseFileSystem.createDirectory(AbsolutePath.withPOSIX(path: "/base/rootIsHere/subdir"), recursive: true) // Create the rooted file system. - let fs = RerootedFileSystemView(baseFileSystem, rootedAt: AbsolutePath("/base/rootIsHere")) + let fs = RerootedFileSystemView(baseFileSystem, rootedAt: AbsolutePath.withPOSIX(path: "/base/rootIsHere")) - let path = AbsolutePath("/test") + let path = AbsolutePath.withPOSIX(path: "/test") try fs.createDirectory(path, recursive: true) let source = path.appending(component: "source") @@ -750,7 +753,7 @@ class FileSystemTests: XCTestCase { func testInMemoryFileSystemFileLock() throws { let fs = InMemoryFileSystem() - let path = AbsolutePath("/") + let path = AbsolutePath.withPOSIX(path: "/") try fs.createDirectory(path) let fileA = path.appending(component: "fileA") @@ -772,11 +775,11 @@ class FileSystemTests: XCTestCase { func testRerootedFileSystemViewFileLock() throws { let inMemoryFS = InMemoryFileSystem() - let rootPath = AbsolutePath("/tmp") + let rootPath = AbsolutePath.withPOSIX(path: "/tmp") try inMemoryFS.createDirectory(rootPath) let fs = RerootedFileSystemView(inMemoryFS, rootedAt: rootPath) - let path = AbsolutePath("/") + let path = AbsolutePath.withPOSIX(path: "/") try fs.createDirectory(path) let fileA = path.appending(component: "fileA") diff --git a/Tests/TSCBasicTests/PathShimTests.swift b/Tests/TSCBasicTests/PathShimTests.swift index f7a2dbf7..c95abe4d 100644 --- a/Tests/TSCBasicTests/PathShimTests.swift +++ b/Tests/TSCBasicTests/PathShimTests.swift @@ -37,19 +37,19 @@ class WalkTests : XCTestCase { #if os(Android) let root = "/system" var expected = [ - AbsolutePath("\(root)/usr"), - AbsolutePath("\(root)/bin"), - AbsolutePath("\(root)/xbin") + AbsolutePath.withPOSIX(path: "\(root)/usr"), + AbsolutePath.withPOSIX(path: "\(root)/bin"), + AbsolutePath.withPOSIX(path: "\(root)/xbin") ] #else let root = "" var expected = [ - AbsolutePath("/usr"), - AbsolutePath("/bin"), - AbsolutePath("/sbin") + AbsolutePath.withPOSIX(path: "/usr"), + AbsolutePath.withPOSIX(path: "/bin"), + AbsolutePath.withPOSIX(path: "/sbin") ] #endif - for x in try! walk(AbsolutePath("\(root)/"), recursively: false) { + for x in try! walk(AbsolutePath.withPOSIX(path: "\(root)/"), recursively: false) { if let i = expected.firstIndex(of: x) { expected.remove(at: i) } diff --git a/Tests/TSCBasicTests/PathTests.swift b/Tests/TSCBasicTests/PathTests.swift index 34f5d79d..2de0fb92 100644 --- a/Tests/TSCBasicTests/PathTests.swift +++ b/Tests/TSCBasicTests/PathTests.swift @@ -12,13 +12,14 @@ import XCTest import Foundation import TSCBasic +import TSCTestSupport class PathTests: XCTestCase { func testBasics() { - XCTAssertEqual(AbsolutePath("/").pathString, "/") - XCTAssertEqual(AbsolutePath("/a").pathString, "/a") - XCTAssertEqual(AbsolutePath("/a/b/c").pathString, "/a/b/c") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").pathString, "/a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a/b/c").pathString, "/a/b/c") XCTAssertEqual(RelativePath(".").pathString, ".") XCTAssertEqual(RelativePath("a").pathString, "a") XCTAssertEqual(RelativePath("a/b/c").pathString, "a/b/c") @@ -26,23 +27,23 @@ class PathTests: XCTestCase { } func testStringInitialization() { - let abs1 = AbsolutePath("/") + let abs1 = AbsolutePath.withPOSIX(path: "/") let abs2 = AbsolutePath(abs1, ".") XCTAssertEqual(abs1, abs2) let rel3 = "." let abs3 = AbsolutePath(abs2, rel3) XCTAssertEqual(abs2, abs3) - let base = AbsolutePath("/base/path") + let base = AbsolutePath.withPOSIX(path: "/base/path") let abs4 = AbsolutePath("/a/b/c", relativeTo: base) - XCTAssertEqual(abs4, AbsolutePath("/a/b/c")) + XCTAssertEqual(abs4, AbsolutePath.withPOSIX(path: "/a/b/c")) let abs5 = AbsolutePath("./a/b/c", relativeTo: base) - XCTAssertEqual(abs5, AbsolutePath("/base/path/a/b/c")) + XCTAssertEqual(abs5, AbsolutePath.withPOSIX(path: "/base/path/a/b/c")) let abs6 = AbsolutePath("~/bla", relativeTo: base) // `~` isn't special - XCTAssertEqual(abs6, AbsolutePath("/base/path/~/bla")) + XCTAssertEqual(abs6, AbsolutePath.withPOSIX(path: "/base/path/~/bla")) } func testStringLiteralInitialization() { - let abs = AbsolutePath("/") + let abs = AbsolutePath.withPOSIX(path: "/") XCTAssertEqual(abs.pathString, "/") let rel1 = RelativePath(".") XCTAssertEqual(rel1.pathString, ".") @@ -51,34 +52,34 @@ class PathTests: XCTestCase { } func testRepeatedPathSeparators() { - XCTAssertEqual(AbsolutePath("/ab//cd//ef").pathString, "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab///cd//ef").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab//cd//ef").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab///cd//ef").pathString, "/ab/cd/ef") XCTAssertEqual(RelativePath("ab//cd//ef").pathString, "ab/cd/ef") XCTAssertEqual(RelativePath("ab//cd///ef").pathString, "ab/cd/ef") } func testTrailingPathSeparators() { - XCTAssertEqual(AbsolutePath("/ab/cd/ef/").pathString, "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/cd/ef//").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab/cd/ef/").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab/cd/ef//").pathString, "/ab/cd/ef") XCTAssertEqual(RelativePath("ab/cd/ef/").pathString, "ab/cd/ef") XCTAssertEqual(RelativePath("ab/cd/ef//").pathString, "ab/cd/ef") } func testDotPathComponents() { - XCTAssertEqual(AbsolutePath("/ab/././cd//ef").pathString, "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/./cd//ef/.").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab/././cd//ef").pathString, "/ab/cd/ef") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab/./cd//ef/.").pathString, "/ab/cd/ef") XCTAssertEqual(RelativePath("ab/./cd/././ef").pathString, "ab/cd/ef") XCTAssertEqual(RelativePath("ab/./cd/ef/.").pathString, "ab/cd/ef") } func testDotDotPathComponents() { - XCTAssertEqual(AbsolutePath("/..").pathString, "/") - XCTAssertEqual(AbsolutePath("/../../../../..").pathString, "/") - XCTAssertEqual(AbsolutePath("/abc/..").pathString, "/") - XCTAssertEqual(AbsolutePath("/abc/../..").pathString, "/") - XCTAssertEqual(AbsolutePath("/../abc").pathString, "/abc") - XCTAssertEqual(AbsolutePath("/../abc/..").pathString, "/") - XCTAssertEqual(AbsolutePath("/../abc/../def").pathString, "/def") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../../../../..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/abc/..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/abc/../..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../abc").pathString, "/abc") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../abc/..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../abc/../def").pathString, "/def") XCTAssertEqual(RelativePath("..").pathString, "..") XCTAssertEqual(RelativePath("../..").pathString, "../..") XCTAssertEqual(RelativePath(".././..").pathString, "../..") @@ -88,8 +89,8 @@ class PathTests: XCTestCase { } func testCombinationsAndEdgeCases() { - XCTAssertEqual(AbsolutePath("///").pathString, "/") - XCTAssertEqual(AbsolutePath("/./").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "///").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/./").pathString, "/") XCTAssertEqual(RelativePath("").pathString, ".") XCTAssertEqual(RelativePath(".").pathString, ".") XCTAssertEqual(RelativePath("./abc").pathString, "abc") @@ -117,11 +118,11 @@ class PathTests: XCTestCase { } func testDirectoryNameExtraction() { - XCTAssertEqual(AbsolutePath("/").dirname, "/") - XCTAssertEqual(AbsolutePath("/a").dirname, "/") - XCTAssertEqual(AbsolutePath("/./a").dirname, "/") - XCTAssertEqual(AbsolutePath("/../..").dirname, "/") - XCTAssertEqual(AbsolutePath("/ab/c//d/").dirname, "/ab/c") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").dirname, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").dirname, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/./a").dirname, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../..").dirname, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/ab/c//d/").dirname, "/ab/c") XCTAssertEqual(RelativePath("ab/c//d/").dirname, "ab/c") XCTAssertEqual(RelativePath("../a").dirname, "..") XCTAssertEqual(RelativePath("../a/..").dirname, ".") @@ -134,10 +135,10 @@ class PathTests: XCTestCase { } func testBaseNameExtraction() { - XCTAssertEqual(AbsolutePath("/").basename, "/") - XCTAssertEqual(AbsolutePath("/a").basename, "a") - XCTAssertEqual(AbsolutePath("/./a").basename, "a") - XCTAssertEqual(AbsolutePath("/../..").basename, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").basename, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").basename, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/./a").basename, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../..").basename, "/") XCTAssertEqual(RelativePath("../..").basename, "..") XCTAssertEqual(RelativePath("../a").basename, "a") XCTAssertEqual(RelativePath("../a/..").basename, "..") @@ -150,10 +151,10 @@ class PathTests: XCTestCase { } func testBaseNameWithoutExt() { - XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, "/") - XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").basenameWithoutExt, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").basenameWithoutExt, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/./a").basenameWithoutExt, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/../..").basenameWithoutExt, "/") XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..") XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a") XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..") @@ -164,8 +165,8 @@ class PathTests: XCTestCase { XCTAssertEqual(RelativePath("").basenameWithoutExt, ".") XCTAssertEqual(RelativePath(".").basenameWithoutExt, ".") - XCTAssertEqual(AbsolutePath("/a.txt").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a.txt").basenameWithoutExt, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a.txt").basenameWithoutExt, "a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/./a.txt").basenameWithoutExt, "a") XCTAssertEqual(RelativePath("../a.bc").basenameWithoutExt, "a") XCTAssertEqual(RelativePath("abc.swift").basenameWithoutExt, "abc") XCTAssertEqual(RelativePath("../a.b.c").basenameWithoutExt, "a.b") @@ -198,57 +199,57 @@ class PathTests: XCTestCase { } func testParentDirectory() { - XCTAssertEqual(AbsolutePath("/").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b").parentDirectory.parentDirectory, AbsolutePath("/yabba")) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").parentDirectory, AbsolutePath.withPOSIX(path: "/")) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").parentDirectory.parentDirectory, AbsolutePath.withPOSIX(path: "/")) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar").parentDirectory, AbsolutePath.withPOSIX(path: "/")) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo/..//").parentDirectory.parentDirectory, AbsolutePath.withPOSIX(path: "/")) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo/..//yabba/a/b").parentDirectory.parentDirectory, AbsolutePath.withPOSIX(path: "/yabba")) } func testConcatenation() { - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString, "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString, "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("..")).pathString, "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("bar")).pathString, "/bar") - XCTAssertEqual(AbsolutePath(AbsolutePath("/foo/bar"), RelativePath("..")).pathString, "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo")).pathString, "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo/..//")).pathString, "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString, "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("")).pathString, "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath(".")).pathString, "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("..")).pathString, "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("bar")).pathString, "/bar") - XCTAssertEqual(AbsolutePath("/foo/bar").appending(RelativePath("..")).pathString, "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo")).pathString, "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo/..//")).pathString, "/") - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString, "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(component: "a").pathString, "/a") - XCTAssertEqual(AbsolutePath("/a").appending(component: "b").pathString, "/a/b") - XCTAssertEqual(AbsolutePath("/").appending(components: "a", "b").pathString, "/a/b") - XCTAssertEqual(AbsolutePath("/a").appending(components: "b", "c").pathString, "/a/b/c") - - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: ".").pathString, "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..").pathString, "/a/b") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString, "/a/b/d") - XCTAssertEqual(AbsolutePath("/").appending(components: "..").pathString, "/") - XCTAssertEqual(AbsolutePath("/").appending(components: ".").pathString, "/") - XCTAssertEqual(AbsolutePath("/").appending(components: "..", "a").pathString, "/a") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/"), RelativePath("")).pathString, "/") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/"), RelativePath(".")).pathString, "/") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/"), RelativePath("..")).pathString, "/") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/"), RelativePath("bar")).pathString, "/bar") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/foo/bar"), RelativePath("..")).pathString, "/foo") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/bar"), RelativePath("../foo")).pathString, "/foo") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/bar"), RelativePath("../foo/..//")).pathString, "/") + XCTAssertEqual(AbsolutePath(AbsolutePath.withPOSIX(path: "/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString, "/yabba/a/b") + + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(RelativePath("")).pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(RelativePath(".")).pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(RelativePath("..")).pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(RelativePath("bar")).pathString, "/bar") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/foo/bar").appending(RelativePath("..")).pathString, "/foo") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar").appending(RelativePath("../foo")).pathString, "/foo") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar").appending(RelativePath("../foo/..//")).pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString, "/yabba/a/b") + + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(component: "a").pathString, "/a") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").appending(component: "b").pathString, "/a/b") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(components: "a", "b").pathString, "/a/b") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a").appending(components: "b", "c").pathString, "/a/b/c") + + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a/b/c").appending(components: ".").pathString, "/a/b/c") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a/b/c").appending(components: "..").pathString, "/a/b") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/a/b/c").appending(components: "..", "d").pathString, "/a/b/d") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(components: "..").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(components: ".").pathString, "/") + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").appending(components: "..", "a").pathString, "/a") XCTAssertEqual(RelativePath("hello").appending(components: "a", "b", "c", "..").pathString, "hello/a/b") XCTAssertEqual(RelativePath("hello").appending(RelativePath("a/b/../c/d")).pathString, "hello/a/c/d") } func testPathComponents() { - XCTAssertEqual(AbsolutePath("/").components, []) - XCTAssertEqual(AbsolutePath("/.").components, []) - XCTAssertEqual(AbsolutePath("/..").components, []) - XCTAssertEqual(AbsolutePath("/bar").components, ["bar"]) - XCTAssertEqual(AbsolutePath("/foo/bar/..").components, ["foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo").components, ["foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").components, []) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b/").components, ["yabba", "a", "b"]) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/").components, []) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/.").components, []) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/..").components, []) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar").components, ["bar"]) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/foo/bar/..").components, ["foo"]) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo").components, ["foo"]) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo/..//").components, []) + XCTAssertEqual(AbsolutePath.withPOSIX(path: "/bar/../foo/..//yabba/a/b/").components, ["yabba", "a", "b"]) XCTAssertEqual(RelativePath("").components, ["."]) XCTAssertEqual(RelativePath(".").components, ["."]) @@ -269,31 +270,31 @@ class PathTests: XCTestCase { } func testRelativePathFromAbsolutePaths() { - XCTAssertEqual(try! AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); - XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); - XCTAssertEqual(try! AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")), RelativePath("../../..")); - XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")), RelativePath("c/d")); - XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")), RelativePath("d")); - XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")), RelativePath("../../b/c/d")); - XCTAssertEqual(try! AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")), RelativePath("../../../a/b/c/d")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/").relative(to: AbsolutePath.withPOSIX(path: "/")), RelativePath(".")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/a/b/c/d").relative(to: AbsolutePath.withPOSIX(path: "/")), RelativePath("a/b/c/d")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/").relative(to: AbsolutePath.withPOSIX(path: "/a/b/c")), RelativePath("../../..")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/a/b/c/d").relative(to: AbsolutePath.withPOSIX(path: "/a/b")), RelativePath("c/d")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/a/b/c/d").relative(to: AbsolutePath.withPOSIX(path: "/a/b/c")), RelativePath("d")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/a/b/c/d").relative(to: AbsolutePath.withPOSIX(path: "/a/c/d")), RelativePath("../../b/c/d")); + XCTAssertEqual(try! AbsolutePath.withPOSIX(path: "/a/b/c/d").relative(to: AbsolutePath.withPOSIX(path: "/b/c/d")), RelativePath("../../../a/b/c/d")); } func testComparison() { - XCTAssertTrue(AbsolutePath("/") <= AbsolutePath("/")); - XCTAssertTrue(AbsolutePath("/abc") < AbsolutePath("/def")); - XCTAssertTrue(AbsolutePath("/2") <= AbsolutePath("/2.1")); - XCTAssertTrue(AbsolutePath("/3.1") > AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2") >= AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2.1") >= AbsolutePath("/2")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/") <= AbsolutePath.withPOSIX(path: "/")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/abc") < AbsolutePath.withPOSIX(path: "/def")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/2") <= AbsolutePath.withPOSIX(path: "/2.1")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/3.1") > AbsolutePath.withPOSIX(path: "/2")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/2") >= AbsolutePath.withPOSIX(path: "/2")); + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/2.1") >= AbsolutePath.withPOSIX(path: "/2")); } func testContains() { - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f").contains(AbsolutePath("/a/b/c/d"))) - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f.swift").contains(AbsolutePath("/a/b/c"))) - XCTAssertTrue(AbsolutePath("/").contains(AbsolutePath("/"))) - XCTAssertTrue(AbsolutePath("/foo/bar").contains(AbsolutePath("/"))) - XCTAssertFalse(AbsolutePath("/foo/bar").contains(AbsolutePath("/foo/bar/baz"))) - XCTAssertFalse(AbsolutePath("/foo/bar").contains(AbsolutePath("/bar"))) + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/a/b/c/d/e/f").contains(AbsolutePath.withPOSIX(path: "/a/b/c/d"))) + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/a/b/c/d/e/f.swift").contains(AbsolutePath.withPOSIX(path: "/a/b/c"))) + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/").contains(AbsolutePath.withPOSIX(path: "/"))) + XCTAssertTrue(AbsolutePath.withPOSIX(path: "/foo/bar").contains(AbsolutePath.withPOSIX(path: "/"))) + XCTAssertFalse(AbsolutePath.withPOSIX(path: "/foo/bar").contains(AbsolutePath.withPOSIX(path: "/foo/bar/baz"))) + XCTAssertFalse(AbsolutePath.withPOSIX(path: "/foo/bar").contains(AbsolutePath.withPOSIX(path: "/bar"))) } func testAbsolutePathValidation() { @@ -334,14 +335,14 @@ class PathTests: XCTestCase { } do { - let foo = Foo(path: AbsolutePath("/path/to/foo")) + let foo = Foo(path: AbsolutePath.withPOSIX(path: "/path/to/foo")) let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) XCTAssertEqual(foo, decodedFoo) } do { - let foo = Foo(path: AbsolutePath("/path/to/../to/foo")) + let foo = Foo(path: AbsolutePath.withPOSIX(path: "/path/to/../to/foo")) let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) XCTAssertEqual(foo, decodedFoo) diff --git a/Tests/TSCBasicTests/miscTests.swift b/Tests/TSCBasicTests/miscTests.swift index 92b8c204..19fa3902 100644 --- a/Tests/TSCBasicTests/miscTests.swift +++ b/Tests/TSCBasicTests/miscTests.swift @@ -48,13 +48,13 @@ class miscTests: XCTestCase { } func testEnvSearchPaths() throws { - let cwd = AbsolutePath("/dummy") + let cwd = AbsolutePath.withPOSIX(path: "/dummy") let paths = getEnvSearchPaths(pathString: "something:.:abc/../.build/debug:/usr/bin:/bin/", currentWorkingDirectory: cwd) XCTAssertEqual(paths, ["/dummy/something", "/dummy", "/dummy/.build/debug", "/usr/bin", "/bin"].map({AbsolutePath($0)})) } func testEmptyEnvSearchPaths() throws { - let cwd = AbsolutePath("/dummy") + let cwd = AbsolutePath.withPOSIX(path: "/dummy") let paths = getEnvSearchPaths(pathString: "", currentWorkingDirectory: cwd) XCTAssertEqual(paths, []) diff --git a/Tests/TSCUtilityTests/ArchiverTests.swift b/Tests/TSCUtilityTests/ArchiverTests.swift index d93d28e2..ec9c553a 100644 --- a/Tests/TSCUtilityTests/ArchiverTests.swift +++ b/Tests/TSCUtilityTests/ArchiverTests.swift @@ -40,8 +40,8 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem() let archiver = ZipArchiver(fileSystem: fileSystem) - let archive = AbsolutePath("/archive.zip") - archiver.extract(from: archive, to: AbsolutePath("/"), completion: { result in + let archive = AbsolutePath.withPOSIX(path: "/archive.zip") + archiver.extract(from: archive, to: AbsolutePath.withPOSIX(path: "/"), completion: { result in XCTAssertResultFailure(result, equals: FileSystemError(.noEntry, archive)) expectation.fulfill() }) @@ -54,8 +54,8 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip") let archiver = ZipArchiver(fileSystem: fileSystem) - let destination = AbsolutePath("/destination") - archiver.extract(from: AbsolutePath("/archive.zip"), to: destination, completion: { result in + let destination = AbsolutePath.withPOSIX(path: "/destination") + archiver.extract(from: AbsolutePath.withPOSIX(path: "/archive.zip"), to: destination, completion: { result in XCTAssertResultFailure(result, equals: FileSystemError(.notDirectory, destination)) expectation.fulfill() }) @@ -68,8 +68,8 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip", "/destination") let archiver = ZipArchiver(fileSystem: fileSystem) - let destination = AbsolutePath("/destination") - archiver.extract(from: AbsolutePath("/archive.zip"), to: destination, completion: { result in + let destination = AbsolutePath.withPOSIX(path: "/destination") + archiver.extract(from: AbsolutePath.withPOSIX(path: "/archive.zip"), to: destination, completion: { result in XCTAssertResultFailure(result, equals: FileSystemError(.notDirectory, destination)) expectation.fulfill() }) diff --git a/Tests/TSCUtilityTests/PkgConfigParserTests.swift b/Tests/TSCUtilityTests/PkgConfigParserTests.swift index 7df6d865..2bb78dc6 100644 --- a/Tests/TSCUtilityTests/PkgConfigParserTests.swift +++ b/Tests/TSCUtilityTests/PkgConfigParserTests.swift @@ -105,8 +105,8 @@ final class PkgConfigParserTests: XCTestCase { "/usr/lib/pkgconfig/foo.pc", "/usr/local/opt/foo/lib/pkgconfig/foo.pc", "/custom/foo.pc") - XCTAssertEqual("/custom/foo.pc", try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [AbsolutePath("/custom")], fileSystem: fs).pathString) - XCTAssertEqual("/custom/foo.pc", try PkgConfig(name: "foo", additionalSearchPaths: [AbsolutePath("/custom")], diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) + XCTAssertEqual("/custom/foo.pc", try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [AbsolutePath.withPOSIX(path: "/custom")], fileSystem: fs).pathString) + XCTAssertEqual("/custom/foo.pc", try PkgConfig(name: "foo", additionalSearchPaths: [AbsolutePath.withPOSIX(path: "/custom")], diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) XCTAssertEqual("/usr/lib/pkgconfig/foo.pc", try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [], fileSystem: fs).pathString) try withCustomEnv(["PKG_CONFIG_PATH": "/usr/local/opt/foo/lib/pkgconfig"]) { XCTAssertEqual("/usr/local/opt/foo/lib/pkgconfig/foo.pc", try PkgConfig(name: "foo", diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) @@ -137,7 +137,7 @@ final class PkgConfigParserTests: XCTestCase { _ = PCFileFinder(diagnostics: diagnostics, brewPrefix: fakePkgConfig.parentDirectory.parentDirectory) } - XCTAssertEqual(PCFileFinder.pkgConfigPaths, [AbsolutePath("/Volumes/BestDrive/pkgconfig")]) + XCTAssertEqual(PCFileFinder.pkgConfigPaths, [AbsolutePath.withPOSIX(path: "/Volumes/BestDrive/pkgconfig")]) } func testAbsolutePathDependency() throws { @@ -164,10 +164,10 @@ final class PkgConfigParserTests: XCTestCase { XCTAssertNoThrow( try PkgConfig( name: "gobject-2.0", - additionalSearchPaths: [AbsolutePath("/usr/local/opt/glib/lib/pkgconfig")], + additionalSearchPaths: [AbsolutePath.withPOSIX(path: "/usr/local/opt/glib/lib/pkgconfig")], diagnostics: DiagnosticsEngine(), fileSystem: fileSystem, - brewPrefix: AbsolutePath("/usr/local"))) + brewPrefix: AbsolutePath.withPOSIX(path: "/usr/local"))) } func testUnevenQuotes() throws { diff --git a/Tests/TSCUtilityTests/SimplePersistenceTests.swift b/Tests/TSCUtilityTests/SimplePersistenceTests.swift index 8ee48488..8405787e 100644 --- a/Tests/TSCUtilityTests/SimplePersistenceTests.swift +++ b/Tests/TSCUtilityTests/SimplePersistenceTests.swift @@ -119,7 +119,7 @@ class SimplePersistenceTests: XCTestCase { func testBasics() throws { let fs = InMemoryFileSystem() let stateFile = AbsolutePath.root.appending(components: "subdir", "state.json") - let foo = Foo(int: 1, path: AbsolutePath("/hello"), fileSystem: fs) + let foo = Foo(int: 1, path: AbsolutePath.withPOSIX(path: "/hello"), fileSystem: fs) // Restoring right now should return false because state is not present. XCTAssertFalse(try foo.restore()) @@ -133,7 +133,7 @@ class SimplePersistenceTests: XCTestCase { foo.int = 5 XCTAssertTrue(try foo.restore()) XCTAssertEqual(foo.int, 1) - XCTAssertEqual(foo.path, AbsolutePath("/hello")) + XCTAssertEqual(foo.path, AbsolutePath.withPOSIX(path: "/hello")) // Modify state's schema version. let newJSON = JSON(["version": 2]) @@ -194,14 +194,14 @@ class SimplePersistenceTests: XCTestCase { """ } - let foo = Foo(int: 1, path: AbsolutePath("/hello"), fileSystem: fs) - XCTAssertEqual(foo.path, AbsolutePath("/hello")) + let foo = Foo(int: 1, path: AbsolutePath.withPOSIX(path: "/hello"), fileSystem: fs) + XCTAssertEqual(foo.path, AbsolutePath.withPOSIX(path: "/hello")) XCTAssertEqual(foo.int, 1) // Load from an older but supported schema state file. XCTAssertTrue(try foo.restore()) - XCTAssertEqual(foo.path, AbsolutePath("/oldpath")) + XCTAssertEqual(foo.path, AbsolutePath.withPOSIX(path: "/oldpath")) XCTAssertEqual(foo.int, 4) } } From 3b3b7797ffe4f406ac779e9c55046a5d2599b1ae Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 1 Mar 2022 21:41:31 +0800 Subject: [PATCH 13/15] Deprecate `AbsolutePath.withPOSIX(path:)` --- Sources/TSCBasic/Path.swift | 27 +++++++------------ Sources/TSCBasic/PathShims.swift | 2 +- .../TSCTestSupport/FileSystemExtensions.swift | 4 +-- Tests/TSCBasicTests/PathShimTests.swift | 8 +++--- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 4a7d12eb..c3134814 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -175,9 +175,9 @@ extension Path { /// Represents an absolute file system path, independently of what (or whether /// anything at all) exists at that path in the file system at any given time. -/// An absolute path always starts with a `/` character, and holds a normalized -/// string representation. This normalization is strictly syntactic, and does -/// not access the file system in any way. +/// An absolute path always holds a normalized string representation. This +/// normalization is strictly syntactic, and does not access the file system +/// in any way. /// /// The absolute path string is normalized by: /// - Collapsing `..` path components @@ -197,7 +197,7 @@ public struct AbsolutePath: Path { /// Underlying type, based on SwiftSystem. public let filepath: FilePath - /// Public initializer with FilePath. + /// Public initializer with `FilePath``. public init(_ filepath: FilePath) { #if os(Windows) if filepath.isAbsolute { @@ -220,6 +220,11 @@ public struct AbsolutePath: Path { /// not interpret leading `~` characters as home directory specifiers). /// The input string will be normalized if needed, as described in the /// documentation for AbsolutePath. + /// + /// On Unix-like systems, an absolute path always starts with a `/` + /// character. Windows normally regards `/` as a relative root, but for + /// compatibility, system drive letter will be appended. Use + /// `try AbsolutePath(validating:)` to avoid such convention. public init(_ absStr: String) { self.init(FilePath(absStr)) } @@ -299,20 +304,6 @@ public struct AbsolutePath: Path { #endif } } - - @available(*, deprecated, message: "use AbsolutePath(_:) directly") - public static func withPOSIX(path: String) -> AbsolutePath { -#if os(Windows) - var filepath = FilePath(path) - precondition(filepath.root != nil) - if !filepath.isAbsolute { - filepath.root = root.filepath.root - } - return AbsolutePath(filepath) -#else - return AbsolutePath(path) -#endif - } } /// Represents a relative file system path. A relative path never starts with diff --git a/Sources/TSCBasic/PathShims.swift b/Sources/TSCBasic/PathShims.swift index a5e17e8f..0c51225a 100644 --- a/Sources/TSCBasic/PathShims.swift +++ b/Sources/TSCBasic/PathShims.swift @@ -29,7 +29,7 @@ public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath { } return resolved.standardized.withUnsafeFileSystemRepresentation { - try! AbsolutePath(validating: String(cString: $0!)) + AbsolutePath(String(cString: $0!)) } #else let pathStr = path.pathString diff --git a/Sources/TSCTestSupport/FileSystemExtensions.swift b/Sources/TSCTestSupport/FileSystemExtensions.swift index 6d8c3986..4feeb1c8 100644 --- a/Sources/TSCTestSupport/FileSystemExtensions.swift +++ b/Sources/TSCTestSupport/FileSystemExtensions.swift @@ -25,7 +25,7 @@ extension InMemoryFileSystem { self.init() for (path, contents) in files { - let path = AbsolutePath.withPOSIX(path: path) + let path = AbsolutePath(path) try! createDirectory(path.parentDirectory, recursive: true) try! writeFileContents(path, bytes: contents) } @@ -52,7 +52,7 @@ extension FileSystem { do { try createDirectory(root, recursive: true) for path in files { - let components = AbsolutePath.withPOSIX(path: path).components + let components = AbsolutePath(path).components let path = root.appending(components: components) try createDirectory(path.parentDirectory, recursive: true) try writeFileContents(path, bytes: "") diff --git a/Tests/TSCBasicTests/PathShimTests.swift b/Tests/TSCBasicTests/PathShimTests.swift index 13baa251..f7a2dbf7 100644 --- a/Tests/TSCBasicTests/PathShimTests.swift +++ b/Tests/TSCBasicTests/PathShimTests.swift @@ -37,9 +37,9 @@ class WalkTests : XCTestCase { #if os(Android) let root = "/system" var expected = [ - AbsolutePath.withPOSIX(path: "\(root)/usr"), - AbsolutePath.withPOSIX(path: "\(root)/bin"), - AbsolutePath.withPOSIX(path: "\(root)/xbin") + AbsolutePath("\(root)/usr"), + AbsolutePath("\(root)/bin"), + AbsolutePath("\(root)/xbin") ] #else let root = "" @@ -49,7 +49,7 @@ class WalkTests : XCTestCase { AbsolutePath("/sbin") ] #endif - for x in try! walk(AbsolutePath.withPOSIX(path: "\(root)/"), recursively: false) { + for x in try! walk(AbsolutePath("\(root)/"), recursively: false) { if let i = expected.firstIndex(of: x) { expected.remove(at: i) } From 6b1b327d57302b620f1cb0440ae796e4d07666b6 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 1 Mar 2022 22:46:42 +0800 Subject: [PATCH 14/15] Make tests run on Windows. --- Sources/TSCBasic/Path.swift | 7 +++--- Sources/TSCTestSupport/misc.swift | 4 ++++ Tests/TSCBasicTests/FileSystemTests.swift | 7 +++++- Tests/TSCBasicTests/PathTests.swift | 11 ++++++--- Tests/TSCBasicTests/TemporaryFileTests.swift | 2 -- Tests/TSCBasicTests/miscTests.swift | 24 ++++++++++++------- .../PkgConfigParserTests.swift | 12 ++++++---- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index c3134814..5d279008 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -204,11 +204,12 @@ public struct AbsolutePath: Path { self.filepath = filepath.lexicallyNormalized() return } - var filepath = filepath.lexicallyNormalized() + var normalizedFilePath = filepath.lexicallyNormalized() guard filepath.root?.string == "\\" else { - preconditionFailure() + preconditionFailure("\(filepath) is not a valid absolute path") } - self.filepath = AbsolutePath.root.filepath.pushing(filepath) + normalizedFilePath.root = AbsolutePath.root.filepath.root + self.filepath = normalizedFilePath.lexicallyNormalized() #else precondition(filepath.isAbsolute) self.filepath = filepath.lexicallyNormalized() diff --git a/Sources/TSCTestSupport/misc.swift b/Sources/TSCTestSupport/misc.swift index 6d58aa17..58a32705 100644 --- a/Sources/TSCTestSupport/misc.swift +++ b/Sources/TSCTestSupport/misc.swift @@ -9,6 +9,7 @@ */ import func XCTest.XCTFail +import struct XCTest.XCTSkip import class Foundation.NSDate import class Foundation.Thread @@ -61,6 +62,9 @@ public func systemQuietly(_ args: String...) throws { /// from different threads, the environment will neither be setup nor restored /// correctly. public func withCustomEnv(_ env: [String: String], body: () throws -> Void) throws { + #if os(Windows) + throw XCTSkip("'withCustomEnv(_:body:)' is broken on Windows") + #endif let state = Array(env.keys).map({ ($0, ProcessEnv.vars[$0]) }) let restore = { for (key, value) in state { diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index df8f0813..6fd04f8a 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -87,7 +87,10 @@ class FileSystemTests: XCTestCase { } } - func testResolvingSymlinks() { + func testResolvingSymlinks() throws { + #if os(Windows) + throw XCTSkip("Symlink resolving on Windows often crashes due to some Foundation bugs.") + #endif // Make sure the root path resolves to itself. XCTAssertEqual(resolveSymlinks(AbsolutePath.root), AbsolutePath.root) @@ -186,6 +189,7 @@ class FileSystemTests: XCTestCase { } } + #if !os(Windows) func testLocalReadableWritable() throws { try testWithTemporaryDirectory { tmpdir in let fs = localFileSystem @@ -253,6 +257,7 @@ class FileSystemTests: XCTestCase { } } } + #endif func testLocalCreateDirectory() throws { let fs = TSCBasic.localFileSystem diff --git a/Tests/TSCBasicTests/PathTests.swift b/Tests/TSCBasicTests/PathTests.swift index 4aae22f0..1ca7cb3f 100644 --- a/Tests/TSCBasicTests/PathTests.swift +++ b/Tests/TSCBasicTests/PathTests.swift @@ -151,10 +151,10 @@ class PathTests: XCTestCase { } func testBaseNameWithoutExt() { - XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, "/") + XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, AbsolutePath.root.pathString) XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a") XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/") + XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, AbsolutePath.root.pathString) XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..") XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a") XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..") @@ -311,7 +311,12 @@ class PathTests: XCTestCase { } func testAbsolutePathValidation() { - XCTAssertNoThrow(try AbsolutePath(validating: "/a/b/c/d")) + #if os(Windows) + let pathString = #"C:\a\b\c\d"# + #else + let pathString = "/a/b/c/d" + #endif + XCTAssertNoThrow(try AbsolutePath(validating: pathString)) XCTAssertThrowsError(try AbsolutePath(validating: "~/a/b/d")) { error in XCTAssertEqual("\(error)", "invalid absolute path '~/a/b/d'") diff --git a/Tests/TSCBasicTests/TemporaryFileTests.swift b/Tests/TSCBasicTests/TemporaryFileTests.swift index 0b2482aa..2d724673 100644 --- a/Tests/TSCBasicTests/TemporaryFileTests.swift +++ b/Tests/TSCBasicTests/TemporaryFileTests.swift @@ -137,7 +137,6 @@ class TemporaryFileTests: XCTestCase { XCTAssertFalse(localFileSystem.isDirectory(pathTwo)) } -#if !os(Windows) /// Check that the temporary file doesn't leak file descriptors. func testLeaks() throws { // We check this by testing that we get back the same FD after a @@ -153,5 +152,4 @@ class TemporaryFileTests: XCTestCase { XCTAssertEqual(initialFD, endFD) #endif } -#endif } diff --git a/Tests/TSCBasicTests/miscTests.swift b/Tests/TSCBasicTests/miscTests.swift index 92b8c204..cc447c88 100644 --- a/Tests/TSCBasicTests/miscTests.swift +++ b/Tests/TSCBasicTests/miscTests.swift @@ -15,8 +15,11 @@ import TSCBasic class miscTests: XCTestCase { func testExecutableLookup() throws { + #if os(Windows) + throw XCTSkip("TODO") + #endif try testWithTemporaryDirectory { path in - + let pathEnv1 = path.appending(component: "pathEnv1") try localFileSystem.createDirectory(pathEnv1) let pathEnvClang = pathEnv1.appending(component: "clang") @@ -28,15 +31,15 @@ class miscTests: XCTestCase { // nil and empty string should fail. XCTAssertNil(lookupExecutablePath(filename: nil, currentWorkingDirectory: path, searchPaths: pathEnv)) XCTAssertNil(lookupExecutablePath(filename: "", currentWorkingDirectory: path, searchPaths: pathEnv)) - + // Absolute path to a binary should return it. var exec = lookupExecutablePath(filename: pathEnvClang.pathString, currentWorkingDirectory: path, searchPaths: pathEnv) XCTAssertEqual(exec, pathEnvClang) - + // This should lookup from PATH variable since executable is not present in cwd. exec = lookupExecutablePath(filename: "clang", currentWorkingDirectory: path, searchPaths: pathEnv) XCTAssertEqual(exec, pathEnvClang) - + // Create the binary relative to cwd and make it executable. let clang = path.appending(component: "clang") try localFileSystem.writeFileContents(clang, bytes: "") @@ -46,18 +49,23 @@ class miscTests: XCTestCase { XCTAssertEqual(exec, clang) } } - + func testEnvSearchPaths() throws { + #if os(Windows) + let pathString = "something;.;abc/../.build/debug;/usr/bin:/bin/" + #else + let pathString = "something:.:abc/../.build/debug:/usr/bin:/bin/" + #endif let cwd = AbsolutePath("/dummy") - let paths = getEnvSearchPaths(pathString: "something:.:abc/../.build/debug:/usr/bin:/bin/", currentWorkingDirectory: cwd) + let paths = getEnvSearchPaths(pathString: pathString, currentWorkingDirectory: cwd) XCTAssertEqual(paths, ["/dummy/something", "/dummy", "/dummy/.build/debug", "/usr/bin", "/bin"].map({AbsolutePath($0)})) } - + func testEmptyEnvSearchPaths() throws { let cwd = AbsolutePath("/dummy") let paths = getEnvSearchPaths(pathString: "", currentWorkingDirectory: cwd) XCTAssertEqual(paths, []) - + let nilPaths = getEnvSearchPaths(pathString: nil, currentWorkingDirectory: cwd) XCTAssertEqual(nilPaths, []) } diff --git a/Tests/TSCUtilityTests/PkgConfigParserTests.swift b/Tests/TSCUtilityTests/PkgConfigParserTests.swift index f8b28ba2..46b3dcb2 100644 --- a/Tests/TSCUtilityTests/PkgConfigParserTests.swift +++ b/Tests/TSCUtilityTests/PkgConfigParserTests.swift @@ -107,14 +107,14 @@ final class PkgConfigParserTests: XCTestCase { "/usr/lib/pkgconfig/foo.pc", "/usr/local/opt/foo/lib/pkgconfig/foo.pc", "/custom/foo.pc") - XCTAssertEqual("/custom/foo.pc", try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [AbsolutePath("/custom")], fileSystem: fs).pathString) - XCTAssertEqual("/custom/foo.pc", try PkgConfig(name: "foo", additionalSearchPaths: [AbsolutePath("/custom")], diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) - XCTAssertEqual("/usr/lib/pkgconfig/foo.pc", try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [], fileSystem: fs).pathString) + XCTAssertEqual(AbsolutePath("/custom/foo.pc"), try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [AbsolutePath("/custom")], fileSystem: fs)) + XCTAssertEqual(AbsolutePath("/custom/foo.pc"), try PkgConfig(name: "foo", additionalSearchPaths: [AbsolutePath("/custom")], diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile) + XCTAssertEqual(AbsolutePath("/usr/lib/pkgconfig/foo.pc"), try PCFileFinder(diagnostics: diagnostics, brewPrefix: nil).locatePCFile(name: "foo", customSearchPaths: [], fileSystem: fs)) try withCustomEnv(["PKG_CONFIG_PATH": "/usr/local/opt/foo/lib/pkgconfig"]) { - XCTAssertEqual("/usr/local/opt/foo/lib/pkgconfig/foo.pc", try PkgConfig(name: "foo", diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) + XCTAssertEqual(AbsolutePath("/usr/local/opt/foo/lib/pkgconfig/foo.pc"), try PkgConfig(name: "foo", diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile) } try withCustomEnv(["PKG_CONFIG_PATH": "/usr/local/opt/foo/lib/pkgconfig:/usr/lib/pkgconfig"]) { - XCTAssertEqual("/usr/local/opt/foo/lib/pkgconfig/foo.pc", try PkgConfig(name: "foo", diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile.pathString) + XCTAssertEqual(AbsolutePath("/usr/local/opt/foo/lib/pkgconfig/foo.pc"), try PkgConfig(name: "foo", diagnostics: diagnostics, fileSystem: fs, brewPrefix: nil).pcFile) } } @@ -142,6 +142,7 @@ final class PkgConfigParserTests: XCTestCase { XCTAssertEqual(PCFileFinder.pkgConfigPaths, [AbsolutePath("/Volumes/BestDrive/pkgconfig")]) } + #if !os(Windows) // pkg-config is not compatible with Windows paths. func testAbsolutePathDependency() throws { let libffiPath = "/usr/local/opt/libffi/lib/pkgconfig/libffi.pc" @@ -171,6 +172,7 @@ final class PkgConfigParserTests: XCTestCase { fileSystem: fileSystem, brewPrefix: AbsolutePath("/usr/local"))) } + #endif func testUnevenQuotes() throws { do { From 82d7f53bdc2245ec89b1eb4ae495a05006e438d9 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 1 Mar 2022 23:17:30 +0800 Subject: [PATCH 15/15] Complete `AbsolutePath.relative(to:)` --- Sources/TSCBasic/Path.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/TSCBasic/Path.swift b/Sources/TSCBasic/Path.swift index 5d279008..ec7180d1 100644 --- a/Sources/TSCBasic/Path.swift +++ b/Sources/TSCBasic/Path.swift @@ -396,9 +396,12 @@ extension AbsolutePath { var relFilePath = FilePath() var filepath = filepath #if os(Windows) - /// TODO: DOS relative path may change the root. - if root != base.root { - throw PathValidationError.differentRoot(pathString, base.pathString) + if self.root != base.root { + guard self.root?.count == 3, + self.root!.hasSuffix(":\\") else { + throw PathValidationError.differentRoot(pathString, base.pathString) + } + relFilePath.root = .init(String(self.root!.dropLast())) } #endif filepath.root = base.filepath.root