11import Foundation
22
33enum CommandRunnerError : LocalizedError {
4- case commandFailed( command: String , arguments: [ String ] , exitCode: Int , output : String )
4+ case commandFailed( command: String , arguments: [ String ] , exitCode: Int , stdout : String , stderr : String )
55
66 var errorDescription : String ? {
77 switch self {
8- case . commandFailed( _, _, let exitCode, let output) :
9- let trimmedOutput = output. trimmingCharacters ( in: . whitespacesAndNewlines)
10- if trimmedOutput. isEmpty {
8+ case . commandFailed( _, _, let exitCode, let stdout, let stderr) :
9+ let trimmedStderr = stderr. trimmingCharacters ( in: . whitespacesAndNewlines)
10+ if !trimmedStderr. isEmpty {
11+ return trimmedStderr
12+ }
13+
14+ let trimmedStdout = stdout. trimmingCharacters ( in: . whitespacesAndNewlines)
15+ if trimmedStdout. isEmpty {
1116 return " Command failed with exit code \( exitCode) . "
1217 }
13- return trimmedOutput
18+ return trimmedStdout
1419 }
1520 }
1621}
@@ -31,14 +36,16 @@ final class CommandRunner: CommandRunning {
3136
3237 func executeStreaming( command: String , arguments: [ String ] = [ ] , onOutput: @escaping ( String ) -> Void ) throws -> String {
3338 let process = Process ( )
34- let pipe = Pipe ( )
39+ let stdoutPipe = Pipe ( )
40+ let stderrPipe = Pipe ( )
3541 let dataLock = NSLock ( )
36- var outputData = Data ( )
42+ var stdoutData = Data ( )
43+ var stderrData = Data ( )
3744
3845 process. executableURL = URL ( fileURLWithPath: command)
3946 process. arguments = arguments
40- process. standardOutput = pipe
41- process. standardError = pipe
47+ process. standardOutput = stdoutPipe
48+ process. standardError = stderrPipe
4249
4350 processLock. lock ( )
4451 currentProcess = process
@@ -51,13 +58,28 @@ final class CommandRunner: CommandRunning {
5158 processLock. unlock ( )
5259 }
5360
54- let fileHandle = pipe. fileHandleForReading
55- fileHandle. readabilityHandler = { handle in
61+ let stdoutHandle = stdoutPipe. fileHandleForReading
62+ let stderrHandle = stderrPipe. fileHandleForReading
63+
64+ stdoutHandle. readabilityHandler = { handle in
5665 let chunkData = handle. availableData
5766 guard !chunkData. isEmpty else { return }
5867
5968 dataLock. lock ( )
60- outputData. append ( chunkData)
69+ stdoutData. append ( chunkData)
70+ dataLock. unlock ( )
71+
72+ if let chunk = String ( data: chunkData, encoding: . utf8) , !chunk. isEmpty {
73+ onOutput ( chunk)
74+ }
75+ }
76+
77+ stderrHandle. readabilityHandler = { handle in
78+ let chunkData = handle. availableData
79+ guard !chunkData. isEmpty else { return }
80+
81+ dataLock. lock ( )
82+ stderrData. append ( chunkData)
6183 dataLock. unlock ( )
6284
6385 if let chunk = String ( data: chunkData, encoding: . utf8) , !chunk. isEmpty {
@@ -68,31 +90,45 @@ final class CommandRunner: CommandRunning {
6890 try process. run ( )
6991 process. waitUntilExit ( )
7092
71- fileHandle. readabilityHandler = nil
93+ stdoutHandle. readabilityHandler = nil
94+ stderrHandle. readabilityHandler = nil
95+
96+ let trailingStdout = stdoutHandle. readDataToEndOfFile ( )
97+ if !trailingStdout. isEmpty {
98+ dataLock. lock ( )
99+ stdoutData. append ( trailingStdout)
100+ dataLock. unlock ( )
101+
102+ if let trailingChunk = String ( data: trailingStdout, encoding: . utf8) , !trailingChunk. isEmpty {
103+ onOutput ( trailingChunk)
104+ }
105+ }
72106
73- let trailingData = fileHandle . readDataToEndOfFile ( )
74- if !trailingData . isEmpty {
107+ let trailingStderr = stderrHandle . readDataToEndOfFile ( )
108+ if !trailingStderr . isEmpty {
75109 dataLock. lock ( )
76- outputData . append ( trailingData )
110+ stderrData . append ( trailingStderr )
77111 dataLock. unlock ( )
78112
79- if let trailingChunk = String ( data: trailingData , encoding: . utf8) , !trailingChunk. isEmpty {
113+ if let trailingChunk = String ( data: trailingStderr , encoding: . utf8) , !trailingChunk. isEmpty {
80114 onOutput ( trailingChunk)
81115 }
82116 }
83117
84- let output = String ( data: outputData, encoding: . utf8) ?? " "
118+ let stdout = String ( data: stdoutData, encoding: . utf8) ?? " "
119+ let stderr = String ( data: stderrData, encoding: . utf8) ?? " "
85120
86121 if process. terminationStatus != 0 {
87122 throw CommandRunnerError . commandFailed (
88123 command: command,
89124 arguments: arguments,
90125 exitCode: Int ( process. terminationStatus) ,
91- output: output
126+ stdout: stdout,
127+ stderr: stderr
92128 )
93129 }
94130
95- return output
131+ return stdout + stderr
96132 }
97133
98134 func cancelCurrentCommand( ) {
0 commit comments