diff --git a/README.md b/README.md index 30d5db8..35e8380 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SwiftCommand -![Platform: macOS/Linux](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-orange) ![Swift versions: 5.6](https://img.shields.io/badge/swift-5.6-blue) +![Platform: macOS/Linux/Windows](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-orange) +![Swift versions: 5.6](https://img.shields.io/badge/swift-5.6-blue) A wrapper around `Foundation.Process`, inspired by Rust's `std::process::Command`. This package makes it easy to call command line diff --git a/Sources/SwiftCommand/Command.swift b/Sources/SwiftCommand/Command.swift index 8fed60e..4295bd9 100644 --- a/Sources/SwiftCommand/Command.swift +++ b/Sources/SwiftCommand/Command.swift @@ -74,7 +74,24 @@ where Stdin: InputSource, Stdout: OutputDestination, Stderr: OutputDestination { } } - + +#if os(Windows) + @inline(__always) + private static var pathVariable: String { "Path" } + @inline(__always) + private static var pathSeparator: Character { ";" } + @inline(__always) + private static var executableExtension: String { ".exe" } +#else + @inline(__always) + private static var pathVariable: String { "PATH" } + @inline(__always) + private static var pathSeparator: Character { ":" } + @inline(__always) + private static var executableExtension: String { "" } +#endif + + /// The path of the executable file that will be invoked when this command /// is spawned. /// @@ -206,15 +223,24 @@ where Stdin: InputSource, Stdout: OutputDestination, Stderr: OutputDestination { where Stdin == UnspecifiedInputSource, Stdout == UnspecifiedOutputDestination, Stderr == UnspecifiedOutputDestination { - guard let environmentPath = ProcessInfo.processInfo - .environment["PATH"] else { + let nameWithExtension: String + if !Self.executableExtension.isEmpty + && !name.hasSuffix(Self.executableExtension) { + nameWithExtension = name + Self.executableExtension + } else { + nameWithExtension = name + } + + guard let environmentPath = + ProcessInfo.processInfo.environment[Self.pathVariable] else { return nil } - guard let executablePath = environmentPath.split(separator: ":").lazy + guard let executablePath = + environmentPath.split(separator: Self.pathSeparator).lazy .compactMap(FilePath.init(substring:)) - .map({ $0.appending(name) }) - .first(where: { + .map({ $0.appending(nameWithExtension) }) + .first(where: { FileManager.default.isExecutableFile(atPath: $0.string) }) else { return nil diff --git a/Sources/SwiftCommand/FileHandle+Async.swift b/Sources/SwiftCommand/FileHandle+Async.swift index 62bbf7e..b27f38f 100644 --- a/Sources/SwiftCommand/FileHandle+Async.swift +++ b/Sources/SwiftCommand/FileHandle+Async.swift @@ -1,6 +1,7 @@ import Foundation fileprivate final actor IOActor { +#if !os(Windows) fileprivate func read( from fd: Int32, into buffer: UnsafeMutableRawBufferPointer @@ -10,9 +11,6 @@ fileprivate final actor IOActor { let read = Darwin.read #elseif canImport(Glibc) let read = Glibc.read -#elseif canImport(CRT) - let read = CRT._read -#else #error("Unsupported platform!") #endif let amount = read(fd, buffer.baseAddress, buffer.count) @@ -29,6 +27,7 @@ fileprivate final actor IOActor { } } } +#endif fileprivate func read( from handle: FileHandle, @@ -174,7 +173,9 @@ extension FileHandle { fileprivate init(file: FileHandle) { self._buffer = _AsyncBytesBuffer(_capacity: Self.bufferSize) +#if !os(Windows) let fileDescriptor = file.fileDescriptor +#endif self._buffer.readFunction = { buf in buf._nextPointer = buf.baseAddress @@ -186,6 +187,12 @@ extension FileHandle { count: capacity ) +#if os(Windows) + let readSize = try await IOActor.default.read( + from: file, + into: bufPtr + ) +#else let readSize: Int if fileDescriptor >= 0 { readSize = try await IOActor.default.read( @@ -198,6 +205,7 @@ extension FileHandle { into: bufPtr ) } +#endif buf._endPointer = buf._nextPointer + readSize return readSize