Skip to content

Commit 2e6136c

Browse files
committed
Revert "Revert "Add support for Process stdin stream""
This reverts commit 81eda02.
1 parent f2de70b commit 2e6136c

File tree

3 files changed

+81
-24
lines changed

3 files changed

+81
-24
lines changed

Sources/TSCBasic/Process.swift

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,11 @@ public final class Process: ObjectIdentifierProtocol {
374374
}
375375
}
376376

377-
/// Launch the subprocess.
378-
public func launch() throws {
377+
/// Launch the subprocess. Returns a WritableByteStream object that can be used to communicate to the process's
378+
/// stdin. If needed, the stream can be closed using the close() API. Otherwise, the stream will be closed
379+
/// automatically.
380+
@discardableResult
381+
public func launch() throws -> WritableByteStream {
379382
precondition(arguments.count > 0 && !arguments[0].isEmpty, "Need at least one argument to launch the process.")
380383

381384
self.launchedLock.withLock {
@@ -401,6 +404,9 @@ public final class Process: ObjectIdentifierProtocol {
401404
_process?.executableURL = executablePath.asURL
402405
_process?.environment = environment
403406

407+
let stdinPipe = Pipe()
408+
_process?.standardInput = stdinPipe
409+
404410
if outputRedirection.redirectsOutput {
405411
let stdoutPipe = Pipe()
406412
let stderrPipe = Pipe()
@@ -423,6 +429,7 @@ public final class Process: ObjectIdentifierProtocol {
423429
}
424430

425431
try _process?.run()
432+
return stdinPipe.fileHandleForWriting
426433
#else
427434
// Initialize the spawn attributes.
428435
#if canImport(Darwin) || os(Android)
@@ -497,14 +504,17 @@ public final class Process: ObjectIdentifierProtocol {
497504
#endif
498505
}
499506

500-
// Workaround for https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=89e435f3559c53084498e9baad22172b64429362
501-
// Change allowing for newer version of glibc
502-
guard let devNull = strdup("/dev/null") else {
503-
throw SystemError.posix_spawn(0, arguments)
504-
}
505-
defer { free(devNull) }
506-
// Open /dev/null as stdin.
507-
posix_spawn_file_actions_addopen(&fileActions, 0, devNull, O_RDONLY, 0)
507+
var stdinPipe: [Int32] = [-1, -1]
508+
try open(pipe: &stdinPipe)
509+
510+
let stdinStream = try LocalFileOutputByteStream(filePointer: fdopen(stdinPipe[1], "wb"), closeOnDeinit: true)
511+
512+
// Dupe the read portion of the remote to 0.
513+
posix_spawn_file_actions_adddup2(&fileActions, stdinPipe[0], 0)
514+
515+
// Close the other side's pipe since it was dupped to 0.
516+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[0])
517+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[1])
508518

509519
var outputPipe: [Int32] = [-1, -1]
510520
var stderrPipe: [Int32] = [-1, -1]
@@ -515,7 +525,7 @@ public final class Process: ObjectIdentifierProtocol {
515525
// Open the write end of the pipe.
516526
posix_spawn_file_actions_adddup2(&fileActions, outputPipe[1], 1)
517527

518-
// Close the other ends of the pipe.
528+
// Close the other ends of the pipe since they were dupped to 1.
519529
posix_spawn_file_actions_addclose(&fileActions, outputPipe[0])
520530
posix_spawn_file_actions_addclose(&fileActions, outputPipe[1])
521531

@@ -527,7 +537,7 @@ public final class Process: ObjectIdentifierProtocol {
527537
try open(pipe: &stderrPipe)
528538
posix_spawn_file_actions_adddup2(&fileActions, stderrPipe[1], 2)
529539

530-
// Close the other ends of the pipe.
540+
// Close the other ends of the pipe since they were dupped to 2.
531541
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[0])
532542
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[1])
533543
}
@@ -548,6 +558,9 @@ public final class Process: ObjectIdentifierProtocol {
548558
throw SystemError.posix_spawn(rv, arguments)
549559
}
550560

561+
// Close the local read end of the input pipe.
562+
try close(fd: stdinPipe[0])
563+
551564
if !outputRedirection.redirectsOutput {
552565
// no stdout or stderr in this case
553566
self.stateLock.withLock {
@@ -556,11 +569,10 @@ public final class Process: ObjectIdentifierProtocol {
556569
} else {
557570
var outputResult: (stdout: Result<[UInt8], Swift.Error>?, stderr: Result<[UInt8], Swift.Error>?)
558571
let outputResultLock = Lock()
559-
560572
let outputClosures = outputRedirection.outputClosures
561573

562-
// Close the write end of the output pipe.
563-
try close(fd: &outputPipe[1])
574+
// Close the local write end of the output pipe.
575+
try close(fd: outputPipe[1])
564576

565577
// Create a thread and start reading the output on it.
566578
let stdoutThread = Thread { [weak self] in
@@ -585,8 +597,8 @@ public final class Process: ObjectIdentifierProtocol {
585597
// Only schedule a thread for stderr if no redirect was requested.
586598
var stderrThread: Thread? = nil
587599
if !outputRedirection.redirectStderr {
588-
// Close the write end of the stderr pipe.
589-
try close(fd: &stderrPipe[1])
600+
// Close the local write end of the stderr pipe.
601+
try close(fd: stderrPipe[1])
590602

591603
// Create a thread and start reading the stderr output on it.
592604
stderrThread = Thread { [weak self] in
@@ -619,6 +631,7 @@ public final class Process: ObjectIdentifierProtocol {
619631
stdoutThread.start()
620632
stderrThread?.start()
621633
}
634+
return stdinStream
622635
#endif // POSIX implementation
623636
}
624637

@@ -830,11 +843,15 @@ private func open(pipe: inout [Int32]) throws {
830843
}
831844

832845
/// Close the given fd.
833-
private func close(fd: inout Int32) throws {
834-
let rv = TSCLibc.close(fd)
835-
guard rv == 0 else {
836-
throw SystemError.close(rv)
846+
private func close(fd: Int32) throws {
847+
func innerClose(_ fd: inout Int32) throws {
848+
let rv = TSCLibc.close(fd)
849+
guard rv == 0 else {
850+
throw SystemError.close(rv)
851+
}
837852
}
853+
var innerFd = fd
854+
try innerClose(&innerFd)
838855
}
839856

840857
extension Process.Error: CustomStringConvertible {
@@ -895,8 +912,26 @@ extension ProcessResult.Error: CustomStringConvertible {
895912
}
896913
}
897914

898-
extension ProcessResult.Error: CustomNSError {
899-
public var errorUserInfo: [String : Any] {
900-
return [NSLocalizedDescriptionKey: self.description]
915+
#if os(Windows)
916+
extension FileHandle: WritableByteStream {
917+
public var position: Int {
918+
return Int(offsetInFile)
919+
}
920+
921+
public func write(_ byte: UInt8) {
922+
write(Data([byte]))
923+
}
924+
925+
public func write<C: Collection>(_ bytes: C) where C.Element == UInt8 {
926+
write(Data(bytes))
927+
}
928+
929+
public func flush() {
930+
synchronizeFile()
931+
}
932+
933+
public func close() throws {
934+
closeFile()
901935
}
902936
}
937+
#endif

Tests/TSCBasicTests/ProcessTests.swift

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

219+
func testStdin() throws {
220+
var stdout = [UInt8]()
221+
let process = Process(args: script("in-to-out"), outputRedirection: .stream(stdout: { stdoutBytes in
222+
stdout += stdoutBytes
223+
}, stderr: { _ in }))
224+
let stdinStream = try process.launch()
225+
226+
stdinStream.write("hello\n")
227+
stdinStream.flush()
228+
229+
try stdinStream.close()
230+
231+
try process.waitUntilExit()
232+
233+
XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\n")
234+
}
235+
219236
func testStdoutStdErr() throws {
220237
// A simple script to check that stdout and stderr are captured separatly.
221238
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)