Skip to content

Commit 65c0f8f

Browse files
committed
Sync latest TSC changes from main
1 parent f0dcf2a commit 65c0f8f

File tree

7 files changed

+97
-54
lines changed

7 files changed

+97
-54
lines changed

swift-tools-support-core/Sources/TSCBasic/Path.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public struct AbsolutePath: Hashable {
134134
/// True if the path is the root directory.
135135
public var isRoot: Bool {
136136
#if os(Windows)
137-
return _impl.string.withCString(encodedAs: UTF16.self, PathIsRootW)
137+
return _impl.string.withCString(encodedAs: UTF16.self, PathCchIsRoot)
138138
#else
139139
return _impl == PathImpl.root
140140
#endif
@@ -243,14 +243,6 @@ public struct RelativePath: Hashable {
243243
return _impl.basename
244244
}
245245

246-
/// Returns the basename without the extension.
247-
public var basenameWithoutExt: String {
248-
if let ext = self.extension {
249-
return String(basename.dropLast(ext.count + 1))
250-
}
251-
return basename
252-
}
253-
254246
/// Suffix (including leading `.` character) if any. Note that a basename
255247
/// that starts with a `.` character is not considered a suffix, nor is a
256248
/// trailing `.` character.

swift-tools-support-core/Sources/TSCBasic/Process.swift

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,11 @@ public final class Process: ObjectIdentifierProtocol {
331331
}
332332
}
333333

334-
/// Launch the subprocess.
335-
public func launch() throws {
334+
/// Launch the subprocess. Returns a WritableByteStream object that can be used to communicate to the process's
335+
/// stdin. If needed, the stream can be closed using the close() API. Otherwise, the stream will be closed
336+
/// automatically.
337+
@discardableResult
338+
public func launch() throws -> WritableByteStream {
336339
precondition(arguments.count > 0 && !arguments[0].isEmpty, "Need at least one argument to launch the process.")
337340
precondition(!launched, "It is not allowed to launch the same process object again.")
338341

@@ -351,12 +354,15 @@ public final class Process: ObjectIdentifierProtocol {
351354
throw Process.Error.missingExecutableProgram(program: executable)
352355
}
353356

354-
#if os(Windows)
357+
#if os(Windows)
355358
_process = Foundation.Process()
356359
_process?.arguments = Array(arguments.dropFirst()) // Avoid including the executable URL twice.
357360
_process?.executableURL = executablePath.asURL
358361
_process?.environment = environment
359362

363+
let stdinPipe = Pipe()
364+
_process?.standardInput = stdinPipe
365+
360366
if outputRedirection.redirectsOutput {
361367
let stdoutPipe = Pipe()
362368
let stderrPipe = Pipe()
@@ -379,6 +385,8 @@ public final class Process: ObjectIdentifierProtocol {
379385
}
380386

381387
try _process?.run()
388+
389+
return stdinPipe.fileHandleForWriting
382390
#else
383391
// Initialize the spawn attributes.
384392
#if canImport(Darwin) || os(Android)
@@ -453,14 +461,17 @@ public final class Process: ObjectIdentifierProtocol {
453461
#endif
454462
}
455463

456-
// Workaround for https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=89e435f3559c53084498e9baad22172b64429362
457-
// Change allowing for newer version of glibc
458-
guard let devNull = strdup("/dev/null") else {
459-
throw SystemError.posix_spawn(0, arguments)
460-
}
461-
defer { free(devNull) }
462-
// Open /dev/null as stdin.
463-
posix_spawn_file_actions_addopen(&fileActions, 0, devNull, O_RDONLY, 0)
464+
var stdinPipe: [Int32] = [-1, -1]
465+
try open(pipe: &stdinPipe)
466+
467+
let stdinStream = try LocalFileOutputByteStream(filePointer: fdopen(stdinPipe[1], "wb"), closeOnDeinit: true)
468+
469+
// Dupe the read portion of the remote to 0.
470+
posix_spawn_file_actions_adddup2(&fileActions, stdinPipe[0], 0)
471+
472+
// Close the other side's pipe since it was dupped to 0.
473+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[0])
474+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[1])
464475

465476
var outputPipe: [Int32] = [-1, -1]
466477
var stderrPipe: [Int32] = [-1, -1]
@@ -471,7 +482,7 @@ public final class Process: ObjectIdentifierProtocol {
471482
// Open the write end of the pipe.
472483
posix_spawn_file_actions_adddup2(&fileActions, outputPipe[1], 1)
473484

474-
// Close the other ends of the pipe.
485+
// Close the other ends of the pipe since they were dupped to 1.
475486
posix_spawn_file_actions_addclose(&fileActions, outputPipe[0])
476487
posix_spawn_file_actions_addclose(&fileActions, outputPipe[1])
477488

@@ -483,7 +494,7 @@ public final class Process: ObjectIdentifierProtocol {
483494
try open(pipe: &stderrPipe)
484495
posix_spawn_file_actions_adddup2(&fileActions, stderrPipe[1], 2)
485496

486-
// Close the other ends of the pipe.
497+
// Close the other ends of the pipe since they were dupped to 2.
487498
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[0])
488499
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[1])
489500
}
@@ -500,11 +511,14 @@ public final class Process: ObjectIdentifierProtocol {
500511
throw SystemError.posix_spawn(rv, arguments)
501512
}
502513

514+
// Close the local read end of the input pipe.
515+
try close(fd: stdinPipe[0])
516+
503517
if outputRedirection.redirectsOutput {
504518
let outputClosures = outputRedirection.outputClosures
505519

506-
// Close the write end of the output pipe.
507-
try close(fd: &outputPipe[1])
520+
// Close the local write end of the output pipe.
521+
try close(fd: outputPipe[1])
508522

509523
// Create a thread and start reading the output on it.
510524
var thread = Thread { [weak self] in
@@ -517,8 +531,8 @@ public final class Process: ObjectIdentifierProtocol {
517531

518532
// Only schedule a thread for stderr if no redirect was requested.
519533
if !outputRedirection.redirectStderr {
520-
// Close the write end of the stderr pipe.
521-
try close(fd: &stderrPipe[1])
534+
// Close the local write end of the stderr pipe.
535+
try close(fd: stderrPipe[1])
522536

523537
// Create a thread and start reading the stderr output on it.
524538
thread = Thread { [weak self] in
@@ -530,6 +544,8 @@ public final class Process: ObjectIdentifierProtocol {
530544
self.stderr.thread = thread
531545
}
532546
}
547+
548+
return stdinStream
533549
#endif // POSIX implementation
534550
}
535551

@@ -731,11 +747,15 @@ private func open(pipe: inout [Int32]) throws {
731747
}
732748

733749
/// Close the given fd.
734-
private func close(fd: inout Int32) throws {
735-
let rv = TSCLibc.close(fd)
736-
guard rv == 0 else {
737-
throw SystemError.close(rv)
750+
private func close(fd: Int32) throws {
751+
func innerClose(_ fd: inout Int32) throws {
752+
let rv = TSCLibc.close(fd)
753+
guard rv == 0 else {
754+
throw SystemError.close(rv)
755+
}
738756
}
757+
var innerFd = fd
758+
try innerClose(&innerFd)
739759
}
740760

741761
extension Process.Error: CustomStringConvertible {
@@ -788,3 +808,27 @@ extension ProcessResult.Error: CustomStringConvertible {
788808
}
789809
}
790810
}
811+
812+
#if os(Windows)
813+
extension FileHandle: WritableByteStream {
814+
public var position: Int {
815+
return Int(offsetInFile)
816+
}
817+
818+
public func write(_ byte: UInt8) {
819+
write(Data([byte]))
820+
}
821+
822+
public func write<C: Collection>(_ bytes: C) where C.Element == UInt8 {
823+
write(Data(bytes))
824+
}
825+
826+
public func flush() {
827+
synchronizeFile()
828+
}
829+
830+
public func close() throws {
831+
closeFile()
832+
}
833+
}
834+
#endif

swift-tools-support-core/Sources/TSCBasic/TerminalController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public final class TerminalController {
110110
/// Computes the terminal type of the stream.
111111
public static func terminalType(_ stream: LocalFileOutputByteStream) -> TerminalType {
112112
#if os(Windows)
113-
return _isatty(_fileno(stream.filePointer)) == 0 ? .file : .tty
113+
return _isatty(_fileno(stream.filePointer)) == 0 ? .file : .dumb
114114
#else
115115
if ProcessEnv.vars["TERM"] == "dumb" {
116116
return .dumb

swift-tools-support-core/Sources/TSCUtility/Platform.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public enum Platform: Equatable {
1616
case android
1717
case darwin
1818
case linux(LinuxFlavor)
19+
case windows
1920

2021
/// Recognized flavors of linux.
2122
public enum LinuxFlavor: Equatable {
@@ -27,6 +28,9 @@ public enum Platform: Equatable {
2728
public static var currentPlatform = Platform._findCurrentPlatform(localFileSystem)
2829
/// Attempt to match `uname` with recognized platforms.
2930
public static func _findCurrentPlatform(_ fs: FileSystem) -> Platform? {
31+
#if os(Windows)
32+
return .windows
33+
#else
3034
guard let uname = try? Process.checkNonZeroExit(args: "uname").spm_chomp().lowercased() else { return nil }
3135
switch uname {
3236
case "darwin":
@@ -36,6 +40,7 @@ public enum Platform: Equatable {
3640
default:
3741
return nil
3842
}
43+
#endif
3944
}
4045

4146
public static func _findCurrentPlatformLinux(_ fs: FileSystem) -> Platform? {

swift-tools-support-core/Tests/TSCBasicTests/PathTests.swift

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class PathTests: XCTestCase {
133133
XCTAssertEqual(RelativePath(".").dirname, ".")
134134
}
135135

136-
func testBasenameExtraction() {
136+
func testBaseNameExtraction() {
137137
XCTAssertEqual(AbsolutePath("/").basename, "/")
138138
XCTAssertEqual(AbsolutePath("/a").basename, "a")
139139
XCTAssertEqual(AbsolutePath("/./a").basename, "a")
@@ -149,28 +149,6 @@ class PathTests: XCTestCase {
149149
XCTAssertEqual(RelativePath(".").basename, ".")
150150
}
151151

152-
func testBasenameWithoutExtExtraction() {
153-
XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, "/")
154-
XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a")
155-
XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a")
156-
XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/")
157-
XCTAssertEqual(AbsolutePath("/a.txt").basenameWithoutExt, "a")
158-
XCTAssertEqual(AbsolutePath("/b/c/../a.txt").basenameWithoutExt, "a")
159-
XCTAssertEqual(AbsolutePath("/b/c/../a.bcd.txt").basenameWithoutExt, "a.bcd")
160-
XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..")
161-
XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a")
162-
XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..")
163-
XCTAssertEqual(RelativePath("a/..").basenameWithoutExt, ".")
164-
XCTAssertEqual(RelativePath("./..").basenameWithoutExt, "..")
165-
XCTAssertEqual(RelativePath("a/../////../////./////").basenameWithoutExt, "..")
166-
XCTAssertEqual(RelativePath("abc").basenameWithoutExt, "abc")
167-
XCTAssertEqual(RelativePath("").basenameWithoutExt, ".")
168-
XCTAssertEqual(RelativePath(".").basenameWithoutExt, ".")
169-
XCTAssertEqual(RelativePath("a.txt").basenameWithoutExt, "a")
170-
XCTAssertEqual(RelativePath("b/c/../a.txt").basenameWithoutExt, "a")
171-
XCTAssertEqual(RelativePath("b/c/../a.bcd.txt").basenameWithoutExt, "a.bcd")
172-
}
173-
174152
func testSuffixExtraction() {
175153
XCTAssertEqual(RelativePath("a").suffix, nil)
176154
XCTAssertEqual(RelativePath("a").extension, nil)
@@ -365,5 +343,7 @@ class PathTests: XCTestCase {
365343

366344
// FIXME: We also need tests for join() operations.
367345

346+
// FIXME: We also need tests for dirname, basename, suffix, etc.
347+
368348
// FIXME: We also need test for stat() operations.
369349
}

swift-tools-support-core/Tests/TSCBasicTests/ProcessTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ class ProcessTests: XCTestCase {
188188
XCTAssertEqual(result2, "hello\n")
189189
}
190190

191+
func testStdin() throws {
192+
var stdout = [UInt8]()
193+
let process = Process(args: script("in-to-out"), outputRedirection: .stream(stdout: { stdoutBytes in
194+
stdout += stdoutBytes
195+
}, stderr: { _ in }))
196+
let stdinStream = try process.launch()
197+
198+
stdinStream.write("hello\n")
199+
stdinStream.flush()
200+
201+
try stdinStream.close()
202+
203+
try process.waitUntilExit()
204+
205+
XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\n")
206+
}
207+
191208
func testStdoutStdErr() throws {
192209
// A simple script to check that stdout and stderr are captured separatly.
193210
do {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python
2+
3+
import sys
4+
5+
sys.stdout.write(sys.stdin.readline())

0 commit comments

Comments
 (0)