Skip to content

Commit 150fbc6

Browse files
committed
various hacks for paths on Windows
This is sufficient for bootstrapping s-p-m on Windows, although the long term maintainable solution is to migrate towards Swift System.
1 parent e2833bd commit 150fbc6

File tree

5 files changed

+93
-12
lines changed

5 files changed

+93
-12
lines changed

Sources/TSCBasic/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
6060
target_link_libraries(TSCBasic PUBLIC
6161
Foundation)
6262
endif()
63+
target_link_libraries(TSCBasic PRIVATE
64+
$<$<PLATFORM_ID:Windows>:Pathcch>)
6365
# NOTE(compnerd) workaround for CMake not setting up include flags yet
6466
set_target_properties(TSCBasic PROPERTIES
6567
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

Sources/TSCBasic/FileSystem.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,19 @@ private class LocalFileSystem: FileSystem {
281281

282282
var currentWorkingDirectory: AbsolutePath? {
283283
let cwdStr = FileManager.default.currentDirectoryPath
284+
285+
#if _runtime(_ObjC)
286+
// The ObjC runtime indicates that the underlying Foundation has ObjC
287+
// interoperability in which case the return type of
288+
// `fileSystemRepresentation` is different from the Swift implementation
289+
// of Foundation.
284290
return try? AbsolutePath(validating: cwdStr)
291+
#else
292+
let fsr: UnsafePointer<Int8> = cwdStr.fileSystemRepresentation
293+
defer { fsr.deallocate() }
294+
295+
return try? AbsolutePath(validating: String(cString: fsr))
296+
#endif
285297
}
286298

287299
func changeCurrentWorkingDirectory(to path: AbsolutePath) throws {

Sources/TSCBasic/Path.swift

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,15 @@ private struct UNIXPath: Path {
429429

430430
var dirname: String {
431431
#if os(Windows)
432-
let dir = string.deletingLastPathComponent
433-
return dir == "" ? "." : dir
432+
let fsr: UnsafePointer<Int8> = string.fileSystemRepresentation
433+
defer { fsr.deallocate() }
434+
435+
let path: String = String(cString: fsr)
436+
return path.withCString(encodedAs: UTF16.self) {
437+
let data = UnsafeMutablePointer(mutating: $0)
438+
PathCchRemoveFileSpec(data, path.count)
439+
return String(decodingCString: data, as: UTF16.self)
440+
}
434441
#else
435442
// FIXME: This method seems too complicated; it should be simplified,
436443
// if possible, and certainly optimized (using UTF8View).
@@ -459,6 +466,13 @@ private struct UNIXPath: Path {
459466
}
460467

461468
var basename: String {
469+
#if os(Windows)
470+
let path: String = self.string
471+
return path.withCString(encodedAs: UTF16.self) {
472+
PathStripPathW(UnsafeMutablePointer(mutating: $0))
473+
return String(decodingCString: $0, as: UTF16.self)
474+
}
475+
#else
462476
// FIXME: This method seems too complicated; it should be simplified,
463477
// if possible, and certainly optimized (using UTF8View).
464478
// Check for a special case of the root directory.
@@ -475,13 +489,17 @@ private struct UNIXPath: Path {
475489
// Otherwise, it's the string from (but not including) the last path
476490
// separator.
477491
return String(string.suffix(from: string.index(after: idx)))
492+
#endif
478493
}
479494

480495
// FIXME: We should investigate if it would be more efficient to instead
481496
// return a path component iterator that does all its work lazily, moving
482497
// from one path separator to the next on-demand.
483498
//
484499
var components: [String] {
500+
#if os(Windows)
501+
return string.components(separatedBy: "\\").filter { !$0.isEmpty }
502+
#else
485503
// FIXME: This isn't particularly efficient; needs optimization, and
486504
// in fact, it might well be best to return a custom iterator so we
487505
// don't have to allocate everything up-front. It would be backed by
@@ -493,6 +511,7 @@ private struct UNIXPath: Path {
493511
} else {
494512
return components
495513
}
514+
#endif
496515
}
497516

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

506525
init(normalizingAbsolutePath path: String) {
507526
#if os(Windows)
508-
self.init(string: path.standardizingPath)
527+
var buffer: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH + 1))
528+
_ = path.withCString(encodedAs: UTF16.self) {
529+
PathCanonicalizeW(&buffer, $0)
530+
}
531+
self.init(string: String(decodingCString: buffer, as: UTF16.self))
509532
#else
510533
precondition(path.first == "/", "Failure normalizing \(path), absolute paths should start with '/'")
511534

@@ -571,7 +594,11 @@ private struct UNIXPath: Path {
571594

572595
init(normalizingRelativePath path: String) {
573596
#if os(Windows)
574-
self.init(string: path.standardizingPath)
597+
var buffer: [WCHAR] = Array<WCHAR>(repeating: 0, count: Int(MAX_PATH + 1))
598+
_ = path.replacingOccurrences(of: "/", with: "\\").withCString(encodedAs: UTF16.self) {
599+
PathCanonicalizeW(&buffer, $0)
600+
}
601+
self.init(string: String(decodingCString: buffer, as: UTF16.self))
575602
#else
576603
precondition(path.first != "/")
577604

@@ -679,6 +706,15 @@ private struct UNIXPath: Path {
679706
}
680707

681708
func suffix(withDot: Bool) -> String? {
709+
#if os(Windows)
710+
let ext = self.string.withCString(encodedAs: UTF16.self) {
711+
PathFindExtensionW($0)
712+
}
713+
var result = String(decodingCString: ext!, as: UTF16.self)
714+
guard result.length > 0 else { return nil }
715+
if !withDot { result.removeFirst(1) }
716+
return result
717+
#else
682718
// FIXME: This method seems too complicated; it should be simplified,
683719
// if possible, and certainly optimized (using UTF8View).
684720
// Find the last path separator, if any.
@@ -700,9 +736,20 @@ private struct UNIXPath: Path {
700736
}
701737
// If we get this far, there is no suffix.
702738
return nil
739+
#endif
703740
}
704741

705742
func appending(component name: String) -> UNIXPath {
743+
#if os(Windows)
744+
var result: PWSTR?
745+
_ = string.withCString(encodedAs: UTF16.self) { root in
746+
name.withCString(encodedAs: UTF16.self) { path in
747+
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
748+
}
749+
}
750+
defer { LocalFree(result) }
751+
return PathImpl(string: String(decodingCString: result!, as: UTF16.self))
752+
#else
706753
assert(!name.contains("/"), "\(name) is invalid path component")
707754

708755
// Handle pseudo paths.
@@ -720,9 +767,20 @@ private struct UNIXPath: Path {
720767
} else {
721768
return PathImpl(string: string + "/" + name)
722769
}
770+
#endif
723771
}
724772

725773
func appending(relativePath: UNIXPath) -> UNIXPath {
774+
#if os(Windows)
775+
var result: PWSTR?
776+
_ = string.withCString(encodedAs: UTF16.self) { root in
777+
relativePath.string.withCString(encodedAs: UTF16.self) { path in
778+
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
779+
}
780+
}
781+
defer { LocalFree(result) }
782+
return PathImpl(string: String(decodingCString: result!, as: UTF16.self))
783+
#else
726784
// Both paths are already normalized. The only case in which we have
727785
// to renormalize their concatenation is if the relative path starts
728786
// with a `..` path component.
@@ -748,6 +806,7 @@ private struct UNIXPath: Path {
748806
} else {
749807
return PathImpl(string: newPathString)
750808
}
809+
#endif
751810
}
752811
}
753812

@@ -795,7 +854,11 @@ extension AbsolutePath {
795854
// Special case, which is a plain path without `..` components. It
796855
// might be an empty path (when self and the base are equal).
797856
let relComps = pathComps.dropFirst(baseComps.count)
857+
#if os(Windows)
858+
result = RelativePath(relComps.joined(separator: "\\"))
859+
#else
798860
result = RelativePath(relComps.joined(separator: "/"))
861+
#endif
799862
} else {
800863
// General case, in which we might well need `..` components to go
801864
// "up" before we can go "down" the directory tree.
@@ -810,7 +873,11 @@ extension AbsolutePath {
810873
// `newBaseComps` followed by what remains in `newPathComps`.
811874
var relComps = Array(repeating: "..", count: newBaseComps.count)
812875
relComps.append(contentsOf: newPathComps)
876+
#if os(Windows)
877+
result = RelativePath(relComps.joined(separator: "\\"))
878+
#else
813879
result = RelativePath(relComps.joined(separator: "/"))
880+
#endif
814881
}
815882
assert(base.appending(result) == self)
816883
return result

Sources/TSCBasic/PathShims.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ import Foundation
2222

2323
/// Returns the "real path" corresponding to `path` by resolving any symbolic links.
2424
public func resolveSymlinks(_ path: AbsolutePath) -> AbsolutePath {
25+
#if os(Windows)
26+
do {
27+
return try AbsolutePath(FileManager.default.destinationOfSymbolicLink(atPath: path.pathString).standardizingPath)
28+
} catch {
29+
return AbsolutePath(path.pathString.standardizingPath)
30+
}
31+
#else
2532
let pathStr = path.pathString
2633

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

4249
return path
50+
#endif
4351
}
4452

4553
/// Creates a new, empty directory at `path`. If needed, any non-existent ancestor paths are also created. If there is

Sources/TSCLibc/libc.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,6 @@
2020
@_exported import TSCclibc
2121

2222
#if os(Windows)
23-
// char *realpath(const char *path, char *resolved_path);
24-
public func realpath(
25-
_ path: String,
26-
_ resolvedPath: UnsafeMutablePointer<CChar>?
27-
) -> UnsafeMutablePointer<CChar>? {
28-
fatalError("realpath is unimplemented")
29-
}
30-
3123
private func __randname(_ buffer: UnsafeMutablePointer<CChar>) {
3224
let alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
3325
_ = (0 ..< 6).map { index in

0 commit comments

Comments
 (0)