Skip to content

Commit 44dd206

Browse files
authored
Improve generate-manual error descriptions (#663)
Adds a human readable description to `GenerateManualError` and updates the non-zero exit code error to include stderr to aid debugging. Fixes: #653
1 parent e3d8a3d commit 44dd206

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

Tools/generate-manual/Extensions/Process+SimpleAPI.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,24 @@ import Foundation
1414
enum SubprocessError: Swift.Error, LocalizedError, CustomStringConvertible {
1515
case missingExecutable(url: URL)
1616
case failedToLaunch(error: Swift.Error)
17-
case nonZeroExitCode(code: Int)
17+
case nonZeroExitCode(code: Int, stderr: String?)
1818

1919
var description: String {
2020
switch self {
2121
case .missingExecutable(let url):
2222
return "No executable at '\(url.standardizedFileURL.path)'."
2323
case .failedToLaunch(let error):
2424
return "Couldn't run command process. \(error.localizedDescription)"
25-
case .nonZeroExitCode(let code):
26-
return "Process returned non-zero exit code '\(code)'."
25+
case .nonZeroExitCode(let code, let stderr):
26+
var description = "Process returned non-zero exit code '\(code)'."
27+
if let stderr = stderr {
28+
description.append(
29+
"""
30+
Standard error:
31+
\(stderr)
32+
""")
33+
}
34+
return description
2735
}
2836
}
2937

@@ -48,7 +56,8 @@ func executeCommand(
4856

4957
let output = Pipe()
5058
process.standardOutput = output
51-
process.standardError = FileHandle.nullDevice
59+
let error = Pipe()
60+
process.standardError = error
5261

5362
if #available(macOS 10.13, *) {
5463
do {
@@ -60,10 +69,15 @@ func executeCommand(
6069
process.launch()
6170
}
6271
let outputData = output.fileHandleForReading.readDataToEndOfFile()
72+
let errorData = error.fileHandleForReading.readDataToEndOfFile()
6373
process.waitUntilExit()
6474

6575
guard process.terminationStatus == 0 else {
66-
throw SubprocessError.nonZeroExitCode(code: Int(process.terminationStatus))
76+
let errorActual = String(data: errorData, encoding: .utf8)?
77+
.trimmingCharacters(in: .whitespacesAndNewlines)
78+
throw SubprocessError.nonZeroExitCode(
79+
code: Int(process.terminationStatus),
80+
stderr: errorActual)
6781
}
6882

6983
let outputActual = String(data: outputData, encoding: .utf8)?

Tools/generate-manual/GenerateManual.swift

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,30 @@ import ArgumentParser
1313
import ArgumentParserToolInfo
1414
import Foundation
1515

16+
enum GenerateManualError: Error {
17+
case failedToRunSubprocess(error: Error)
18+
case unableToParseToolOutput(error: Error)
19+
case unsupportedDumpHelpVersion(expected: Int, found: Int)
20+
case failedToGenerateManualPages(error: Error)
21+
}
22+
23+
extension GenerateManualError: CustomStringConvertible {
24+
var description: String {
25+
switch self {
26+
case let .failedToRunSubprocess(error):
27+
"Failed to run subprocess: \(error)"
28+
case let .unableToParseToolOutput(error):
29+
"Failed to parse tool output: \(error)"
30+
case let .unsupportedDumpHelpVersion(expected, found):
31+
"Unsupported dump help version, expected '\(expected)' but found: '\(found)'"
32+
case let .failedToGenerateManualPages(error):
33+
"Failed to generated manual pages: \(error)"
34+
}
35+
}
36+
}
37+
1638
@main
1739
struct GenerateManual: ParsableCommand {
18-
enum Error: Swift.Error {
19-
case failedToRunSubprocess(error: Swift.Error)
20-
case unableToParseToolOutput(error: Swift.Error)
21-
case unsupportedDumpHelpVersion(expected: Int, found: Int)
22-
case failedToGenerateManualPages(error: Swift.Error)
23-
}
2440

2541
static let configuration = CommandConfiguration(
2642
commandName: "generate-manual",
@@ -70,25 +86,25 @@ struct GenerateManual: ParsableCommand {
7086
let output = try executeCommand(executable: tool, arguments: ["--experimental-dump-help"])
7187
data = output.data(using: .utf8) ?? Data()
7288
} catch {
73-
throw Error.failedToRunSubprocess(error: error)
89+
throw GenerateManualError.failedToRunSubprocess(error: error)
7490
}
7591

7692
do {
7793
let toolInfoThin = try JSONDecoder().decode(ToolInfoHeader.self, from: data)
7894
guard toolInfoThin.serializationVersion == 0 else {
79-
throw Error.unsupportedDumpHelpVersion(
95+
throw GenerateManualError.unsupportedDumpHelpVersion(
8096
expected: 0,
8197
found: toolInfoThin.serializationVersion)
8298
}
8399
} catch {
84-
throw Error.unableToParseToolOutput(error: error)
100+
throw GenerateManualError.unableToParseToolOutput(error: error)
85101
}
86102

87103
let toolInfo: ToolInfoV0
88104
do {
89105
toolInfo = try JSONDecoder().decode(ToolInfoV0.self, from: data)
90106
} catch {
91-
throw Error.unableToParseToolOutput(error: error)
107+
throw GenerateManualError.unableToParseToolOutput(error: error)
92108
}
93109

94110
do {
@@ -100,7 +116,7 @@ struct GenerateManual: ParsableCommand {
100116
savingTo: URL(fileURLWithPath: outputDirectory))
101117
}
102118
} catch {
103-
throw Error.failedToGenerateManualPages(error: error)
119+
throw GenerateManualError.failedToGenerateManualPages(error: error)
104120
}
105121
}
106122

0 commit comments

Comments
 (0)