Skip to content

various hacks for paths on Windows #114

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/TSCBasic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
target_link_libraries(TSCBasic PUBLIC
Foundation)
endif()
target_link_libraries(TSCBasic PRIVATE
$<$<PLATFORM_ID:Windows>:Pathcch>)
# NOTE(compnerd) workaround for CMake not setting up include flags yet
set_target_properties(TSCBasic PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
Expand Down
12 changes: 12 additions & 0 deletions Sources/TSCBasic/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,19 @@ private class LocalFileSystem: FileSystem {

var currentWorkingDirectory: AbsolutePath? {
let cwdStr = FileManager.default.currentDirectoryPath

#if _runtime(_ObjC)
// The ObjC runtime indicates that the underlying Foundation has ObjC
// interoperability in which case the return type of
// `fileSystemRepresentation` is different from the Swift implementation
// of Foundation.
return try? AbsolutePath(validating: cwdStr)
#else
let fsr: UnsafePointer<Int8> = cwdStr.fileSystemRepresentation
defer { fsr.deallocate() }

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

func changeCurrentWorkingDirectory(to path: AbsolutePath) throws {
Expand Down
75 changes: 71 additions & 4 deletions Sources/TSCBasic/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,15 @@ private struct UNIXPath: Path {

var dirname: String {
#if os(Windows)
let dir = string.deletingLastPathComponent
return dir == "" ? "." : dir
let fsr: UnsafePointer<Int8> = 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).
Expand Down Expand Up @@ -459,6 +466,13 @@ private struct UNIXPath: Path {
}

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.
Expand All @@ -475,13 +489,17 @@ private struct UNIXPath: Path {
// 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 }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, this should use PathSkipRootW and then split on \ and /.

#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
Expand All @@ -493,6 +511,7 @@ private struct UNIXPath: Path {
} else {
return components
}
#endif
}

var parentDirectory: UNIXPath {
Expand All @@ -505,7 +524,11 @@ private struct UNIXPath: Path {

init(normalizingAbsolutePath path: String) {
#if os(Windows)
self.init(string: path.standardizingPath)
var buffer: [WCHAR] = Array<WCHAR>(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 '/'")

Expand Down Expand Up @@ -571,7 +594,11 @@ private struct UNIXPath: Path {

init(normalizingRelativePath path: String) {
#if os(Windows)
self.init(string: path.standardizingPath)
var buffer: [WCHAR] = Array<WCHAR>(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 != "/")

Expand Down Expand Up @@ -679,6 +706,15 @@ private struct UNIXPath: Path {
}

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.
Expand All @@ -700,9 +736,20 @@ private struct UNIXPath: Path {
}
// 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.
Expand All @@ -720,9 +767,20 @@ private struct UNIXPath: Path {
} 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.
Expand All @@ -748,6 +806,7 @@ private struct UNIXPath: Path {
} else {
return PathImpl(string: newPathString)
}
#endif
}
}

Expand Down Expand Up @@ -795,7 +854,11 @@ extension AbsolutePath {
// 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.
Expand All @@ -810,7 +873,11 @@ extension AbsolutePath {
// `newBaseComps` followed by what remains in `newPathComps`.
var relComps = Array(repeating: "..", count: newBaseComps.count)
relComps.append(contentsOf: newPathComps)
#if os(Windows)
result = RelativePath(relComps.joined(separator: "\\"))
#else
result = RelativePath(relComps.joined(separator: "/"))
#endif
}
assert(base.appending(result) == self)
return result
Expand Down
8 changes: 8 additions & 0 deletions Sources/TSCBasic/PathShims.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import Foundation

/// Returns the "real path" corresponding to `path` by resolving any symbolic links.
public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath {
#if os(Windows)
do {
return try AbsolutePath(FileManager.default.destinationOfSymbolicLink(atPath: path.pathString).standardizingPath)
} catch {
return AbsolutePath(path.pathString.standardizingPath)
}
#else
let pathStr = path.pathString

// FIXME: We can't use FileManager's destinationOfSymbolicLink because
Expand All @@ -40,6 +47,7 @@ public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath {
}

return path
#endif
}

/// Creates a new, empty directory at `path`. If needed, any non-existent ancestor paths are also created. If there is
Expand Down
8 changes: 0 additions & 8 deletions Sources/TSCLibc/libc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@
@_exported import TSCclibc

#if os(Windows)
// char *realpath(const char *path, char *resolved_path);
public func realpath(
_ path: String,
_ resolvedPath: UnsafeMutablePointer<CChar>?
) -> UnsafeMutablePointer<CChar>? {
fatalError("realpath is unimplemented")
}

private func __randname(_ buffer: UnsafeMutablePointer<CChar>) {
let alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
_ = (0 ..< 6).map { index in
Expand Down