@@ -3,7 +3,7 @@ import os
33
44final class ProcessProgressStreamRunner : Sendable {
55 typealias OutputHandler = @Sendable ( String , Progress ) -> Void
6- typealias FailureHandler = @Sendable ( Process ) -> Error
6+ typealias FailureHandler = @Sendable ( Process , String , String ) -> Error
77 typealias SuccessHandler = @Sendable ( ) -> Error ?
88
99 private let process : Process
@@ -54,20 +54,21 @@ final class ProcessProgressStreamRunner: Sendable {
5454 let stdErrPipe = Pipe ( )
5555 process. standardError = stdErrPipe
5656
57- let handleData : @Sendable ( FileHandle ) -> Void = { [ weak self] handle in
57+ let handleData : @Sendable ( FileHandle , OutputStream ) -> Void = { [ weak self] handle, stream in
5858 guard let self else { return }
5959 let data = handle. availableData
6060 guard data. isEmpty == false else { return }
6161
6262 let string = String ( decoding: data, as: UTF8 . self)
6363 self . continuation. withLock {
64+ self . append ( data, to: stream)
6465 self . outputHandler ( string, self . progress)
6566 _ = $0? . yield ( self . progress)
6667 }
6768 }
6869
69- stdOutPipe. fileHandleForReading. readabilityHandler = handleData
70- stdErrPipe. fileHandleForReading. readabilityHandler = handleData
70+ stdOutPipe. fileHandleForReading. readabilityHandler = { handleData ( $0 , . stdout ) }
71+ stdErrPipe. fileHandleForReading. readabilityHandler = { handleData ( $0 , . stderr ) }
7172
7273 process. terminationHandler = { [ weak self] process in
7374 self ? . finish ( process: process)
@@ -95,7 +96,8 @@ final class ProcessProgressStreamRunner: Sendable {
9596 consumeRemainingOutput ( )
9697
9798 guard process. terminationReason == . exit, process. terminationStatus == 0 else {
98- finish ( throwing: failureHandler ( process) )
99+ let output = output ( )
100+ finish ( throwing: failureHandler ( process, output. stdout, output. stderr) )
99101 return
100102 }
101103
@@ -126,20 +128,53 @@ final class ProcessProgressStreamRunner: Sendable {
126128 }
127129
128130 private func consumeRemainingOutput( ) {
129- consumeRemainingOutput ( from: process. standardOutput as? Pipe )
130- consumeRemainingOutput ( from: process. standardError as? Pipe )
131+ consumeRemainingOutput ( from: process. standardOutput as? Pipe , stream : . stdout )
132+ consumeRemainingOutput ( from: process. standardError as? Pipe , stream : . stderr )
131133 }
132134
133- private func consumeRemainingOutput( from pipe: Pipe ? ) {
135+ private func consumeRemainingOutput( from pipe: Pipe ? , stream : OutputStream ) {
134136 guard let pipe else { return }
135137
136138 let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
137139 guard data. isEmpty == false else { return }
138140
139141 let string = String ( decoding: data, as: UTF8 . self)
140142 continuation. withLock {
143+ append ( data, to: stream)
141144 outputHandler ( string, progress)
142145 _ = $0? . yield ( progress)
143146 }
144147 }
148+
149+ private let capturedOutput = OSAllocatedUnfairLock < OutputStorage > ( initialState: OutputStorage ( ) )
150+
151+ private func append( _ data: Data , to stream: OutputStream ) {
152+ capturedOutput. withLock {
153+ switch stream {
154+ case . stdout:
155+ $0. stdout. append ( data)
156+ case . stderr:
157+ $0. stderr. append ( data)
158+ }
159+ }
160+ }
161+
162+ private func output( ) -> ( stdout: String , stderr: String ) {
163+ capturedOutput. withLock {
164+ (
165+ String ( data: $0. stdout, encoding: . utf8) ?? " " ,
166+ String ( data: $0. stderr, encoding: . utf8) ?? " "
167+ )
168+ }
169+ }
170+
171+ private enum OutputStream {
172+ case stdout
173+ case stderr
174+ }
175+
176+ private struct OutputStorage : Sendable {
177+ var stdout = Data ( )
178+ var stderr = Data ( )
179+ }
145180}
0 commit comments