Skip to content

Commit a62f612

Browse files
authored
Merge pull request #2 from XcodesOrg/betterruntimedonwloaderrors
better error messages when downloading
2 parents 5bee2a2 + 15b6d80 commit a62f612

4 files changed

Lines changed: 64 additions & 19 deletions

File tree

Sources/XcodesKit/Services/Aria2DownloadService.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ public struct Aria2DownloadService: Sendable {
3838

3939
progress.updateFromAria2(string: string)
4040
},
41-
failureHandler: { process in
41+
failureHandler: { process, stdout, stderr in
4242
if let aria2cError = Aria2CError(exitStatus: process.terminationStatus) {
4343
return aria2cError
4444
} else {
45-
return ProcessExecutionError(process: process, standardOutput: "", standardError: "")
45+
return ProcessExecutionError(
46+
process: process,
47+
terminationStatus: process.terminationStatus,
48+
standardOutput: stdout,
49+
standardError: stderr
50+
)
4651
}
4752
},
4853
successHandler: {

Sources/XcodesKit/Services/XcodebuildRuntimeDownloadService.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ public struct XcodebuildRuntimeDownloadService: Sendable {
3434
outputHandler: { string, progress in
3535
progress.updateFromXcodebuild(text: string)
3636
},
37-
failureHandler: { process in
38-
ProcessExecutionError(process: process, standardOutput: "", standardError: "")
37+
failureHandler: { process, stdout, stderr in
38+
ProcessExecutionError(
39+
process: process,
40+
terminationStatus: process.terminationStatus,
41+
standardOutput: stdout,
42+
standardError: stderr
43+
)
3944
}
4045
).stream()
4146
}

Sources/XcodesKit/Shell/ProcessProgressStream.swift

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import os
33

44
final 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
}

Tests/XcodesKitTests/XcodesKitTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2041,7 +2041,7 @@ final class XcodesKitTests: XCTestCase {
20412041
collector.append(string)
20422042
progress.updateFromXcodebuild(text: string)
20432043
},
2044-
failureHandler: { process in
2044+
failureHandler: { process, _, _ in
20452045
ProcessExecutionError(process: process, standardOutput: "", standardError: "")
20462046
}
20472047
).stream()
@@ -2074,7 +2074,7 @@ final class XcodesKitTests: XCTestCase {
20742074
outputHandler: { string, _ in
20752075
collector.append(string)
20762076
},
2077-
failureHandler: { process in
2077+
failureHandler: { process, _, _ in
20782078
ProcessExecutionError(process: process, standardOutput: "", standardError: "")
20792079
}
20802080
).stream()
@@ -2087,27 +2087,27 @@ final class XcodesKitTests: XCTestCase {
20872087

20882088
func testProcessProgressStreamRunnerThrowsFailureHandlerError() async {
20892089
enum TestError: Error, Equatable {
2090-
case failed(Int32)
2090+
case failed(Int32, String, String)
20912091
}
20922092

20932093
let process = Process()
20942094
process.executableURL = URL(fileURLWithPath: "/bin/sh")
2095-
process.arguments = ["-c", "exit 12"]
2095+
process.arguments = ["-c", "printf 'stdout text'; printf 'stderr text' >&2; exit 12"]
20962096

20972097
let stream = ProcessProgressStreamRunner(
20982098
process: process,
20992099
progress: Progress(),
21002100
outputHandler: { _, _ in },
2101-
failureHandler: { process in
2102-
TestError.failed(process.terminationStatus)
2101+
failureHandler: { process, stdout, stderr in
2102+
TestError.failed(process.terminationStatus, stdout, stderr)
21032103
}
21042104
).stream()
21052105

21062106
do {
21072107
for try await _ in stream {}
21082108
XCTFail("Expected process failure to throw")
21092109
} catch {
2110-
XCTAssertEqual(error as? TestError, .failed(12))
2110+
XCTAssertEqual(error as? TestError, .failed(12, "stdout text", "stderr text"))
21112111
}
21122112
}
21132113

0 commit comments

Comments
 (0)