Skip to content

Commit cf75b84

Browse files
committed
Bring back Sergio's "Add support for Process stdin strean"
This PR resurrects swiftlang#122, which we required in order to be able to forward standard input in the new swift-driver to the compile jobs it spawns. The change was previously reverted in swiftlang#143, due to an error seen on Linux.
1 parent f2de70b commit cf75b84

File tree

3 files changed

+92
-20
lines changed

3 files changed

+92
-20
lines changed

Sources/TSCBasic/Process.swift

Lines changed: 73 additions & 20 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,8 @@ public final class Process: ObjectIdentifierProtocol {
423429
}
424430

425431
try _process?.run()
432+
433+
return stdinPipe.fileHandleForWriting
426434
#else
427435
// Initialize the spawn attributes.
428436
#if canImport(Darwin) || os(Android)
@@ -497,14 +505,27 @@ public final class Process: ObjectIdentifierProtocol {
497505
#endif
498506
}
499507

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)
508+
var stdinPipe: [Int32] = [-1, -1]
509+
try open(pipe: &stdinPipe)
510+
511+
let stdinStream = try LocalFileOutputByteStream(filePointer: fdopen(stdinPipe[1], "wb"),
512+
closeOnDeinit: true)
513+
514+
// Dupe the read portion of the remote to 0.
515+
posix_spawn_file_actions_adddup2(&fileActions, stdinPipe[0], STDIN_FILENO)
516+
517+
// Close the other side's pipe since it was dupped to 0.
518+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[0])
519+
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[1])
520+
521+
// // Workaround for https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=89e435f3559c53084498e9baad22172b64429362
522+
// // Change allowing for newer version of glibc
523+
// guard let devNull = strdup("/dev/null") else {
524+
// throw SystemError.posix_spawn(0, arguments)
525+
// }
526+
// defer { free(devNull) }
527+
// // Open /dev/null as stdin.
528+
// posix_spawn_file_actions_addopen(&fileActions, 0, devNull, O_RDONLY, 0)
508529

509530
var outputPipe: [Int32] = [-1, -1]
510531
var stderrPipe: [Int32] = [-1, -1]
@@ -515,7 +536,7 @@ public final class Process: ObjectIdentifierProtocol {
515536
// Open the write end of the pipe.
516537
posix_spawn_file_actions_adddup2(&fileActions, outputPipe[1], 1)
517538

518-
// Close the other ends of the pipe.
539+
// Close the other ends of the pipe since they were dupped to 1.
519540
posix_spawn_file_actions_addclose(&fileActions, outputPipe[0])
520541
posix_spawn_file_actions_addclose(&fileActions, outputPipe[1])
521542

@@ -527,7 +548,7 @@ public final class Process: ObjectIdentifierProtocol {
527548
try open(pipe: &stderrPipe)
528549
posix_spawn_file_actions_adddup2(&fileActions, stderrPipe[1], 2)
529550

530-
// Close the other ends of the pipe.
551+
// Close the other ends of the pipe since they were dupped to 2.
531552
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[0])
532553
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[1])
533554
}
@@ -548,6 +569,9 @@ public final class Process: ObjectIdentifierProtocol {
548569
throw SystemError.posix_spawn(rv, arguments)
549570
}
550571

572+
// Close the local read end of the input pipe.
573+
try close(fd: stdinPipe[0])
574+
551575
if !outputRedirection.redirectsOutput {
552576
// no stdout or stderr in this case
553577
self.stateLock.withLock {
@@ -559,8 +583,8 @@ public final class Process: ObjectIdentifierProtocol {
559583

560584
let outputClosures = outputRedirection.outputClosures
561585

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

565589
// Create a thread and start reading the output on it.
566590
let stdoutThread = Thread { [weak self] in
@@ -585,8 +609,8 @@ public final class Process: ObjectIdentifierProtocol {
585609
// Only schedule a thread for stderr if no redirect was requested.
586610
var stderrThread: Thread? = nil
587611
if !outputRedirection.redirectStderr {
588-
// Close the write end of the stderr pipe.
589-
try close(fd: &stderrPipe[1])
612+
// Close the local write end of the stderr pipe.
613+
try close(fd: stderrPipe[1])
590614

591615
// Create a thread and start reading the stderr output on it.
592616
stderrThread = Thread { [weak self] in
@@ -619,6 +643,7 @@ public final class Process: ObjectIdentifierProtocol {
619643
stdoutThread.start()
620644
stderrThread?.start()
621645
}
646+
return stdinStream
622647
#endif // POSIX implementation
623648
}
624649

@@ -830,11 +855,15 @@ private func open(pipe: inout [Int32]) throws {
830855
}
831856

832857
/// 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)
858+
private func close(fd: Int32) throws {
859+
func innerClose(_ fd: inout Int32) throws {
860+
let rv = TSCLibc.close(fd)
861+
guard rv == 0 else {
862+
throw SystemError.close(rv)
863+
}
837864
}
865+
var innerFd = fd
866+
try innerClose(&innerFd)
838867
}
839868

840869
extension Process.Error: CustomStringConvertible {
@@ -900,3 +929,27 @@ extension ProcessResult.Error: CustomNSError {
900929
return [NSLocalizedDescriptionKey: self.description]
901930
}
902931
}
932+
933+
#if os(Windows)
934+
extension FileHandle: WritableByteStream {
935+
public var position: Int {
936+
return Int(offsetInFile)
937+
}
938+
939+
public func write(_ byte: UInt8) {
940+
write(Data([byte]))
941+
}
942+
943+
public func write<C: Collection>(_ bytes: C) where C.Element == UInt8 {
944+
write(Data(bytes))
945+
}
946+
947+
public func flush() {
948+
synchronizeFile()
949+
}
950+
951+
public func close() throws {
952+
closeFile()
953+
}
954+
}
955+
#endif

Tests/TSCBasicTests/ProcessTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,20 @@ 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+
stdinStream.write("hello\n")
226+
stdinStream.flush()
227+
try stdinStream.close()
228+
try process.waitUntilExit()
229+
230+
XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\n")
231+
}
232+
219233
func testStdoutStdErr() throws {
220234
// A simple script to check that stdout and stderr are captured separatly.
221235
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)