Skip to content

Commit ff7d30a

Browse files
committed
error handling
1 parent 1a249d8 commit ff7d30a

File tree

1 file changed

+57
-41
lines changed

1 file changed

+57
-41
lines changed

Plugins/AWSLambdaPackager/Plugin.swift

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct AWSLambdaPackager: CommandPlugin {
102102
)
103103
let productPath = buildOutputPath.appending(product.name)
104104
guard FileManager.default.fileExists(atPath: productPath.string) else {
105-
throw Errors.productExecutableNotFound("could not find executable for '\(product.name)', expected at '\(productPath)'")
105+
throw Errors.productExecutableNotFound(product.name)
106106
}
107107
builtProducts[.init(product)] = productPath
108108
}
@@ -132,7 +132,7 @@ struct AWSLambdaPackager: CommandPlugin {
132132
parameters: parameters
133133
)
134134
guard let artifact = result.executableArtifact(for: product) else {
135-
throw Errors.unknownExecutable("no executable artifacts found for \(product.name)")
135+
throw Errors.productExecutableNotFound(product.name)
136136
}
137137
results[.init(product)] = artifact.path
138138
}
@@ -197,14 +197,14 @@ struct AWSLambdaPackager: CommandPlugin {
197197
print("\(executable.string) \(arguments.joined(separator: " "))")
198198
}
199199

200-
let sync = DispatchGroup()
201200
var output = ""
201+
let outputSync = DispatchGroup()
202202
let outputQueue = DispatchQueue(label: "AWSLambdaPackager.output")
203203
let outputHandler = { (data: Data?) in
204204
dispatchPrecondition(condition: .onQueue(outputQueue))
205205

206-
sync.enter()
207-
defer { sync.leave() }
206+
outputSync.enter()
207+
defer { outputSync.leave() }
208208

209209
guard let _output = data.flatMap({ String(data: $0, encoding: .utf8)?.trimmingCharacters(in: CharacterSet(["\n"])) }), !_output.isEmpty else {
210210
return
@@ -216,34 +216,36 @@ struct AWSLambdaPackager: CommandPlugin {
216216
output += _output
217217
}
218218

219-
let stdoutPipe = Pipe()
220-
stdoutPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputQueue.async { outputHandler(fileHandle.availableData) } }
221-
let stderrPipe = Pipe()
222-
stderrPipe.fileHandleForReading.readabilityHandler = { fileHandle in outputQueue.async { outputHandler(fileHandle.availableData) } }
219+
let pipe = Pipe()
220+
pipe.fileHandleForReading.readabilityHandler = { fileHandle in outputQueue.async { outputHandler(fileHandle.availableData) } }
223221

224222
let process = Process()
225-
process.standardOutput = stdoutPipe
226-
process.standardError = stderrPipe
223+
process.standardOutput = pipe
224+
process.standardError = pipe
227225
process.executableURL = URL(fileURLWithPath: executable.string)
228226
process.arguments = arguments
229227
if let workingDirectory = customWorkingDirectory {
230228
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.string)
231229
}
232230
process.terminationHandler = { _ in
233231
outputQueue.async {
234-
outputHandler(try? stdoutPipe.fileHandleForReading.readToEnd())
235-
outputHandler(try? stderrPipe.fileHandleForReading.readToEnd())
232+
outputHandler(try? pipe.fileHandleForReading.readToEnd())
236233
}
237234
}
238235

239236
try process.run()
240237
process.waitUntilExit()
241238

242239
// wait for output to be full processed
243-
sync.wait()
240+
outputSync.wait()
244241

245242
if process.terminationStatus != 0 {
246-
throw Errors.processFailed(process.terminationStatus)
243+
// print output on failure and if not already printed
244+
if logLevel < .output {
245+
print(output)
246+
fflush(stdout)
247+
}
248+
throw Errors.processFailed([executable.string] + arguments, process.terminationStatus)
247249
}
248250

249251
return output
@@ -282,7 +284,7 @@ private struct Configuration: CustomStringConvertible {
282284
if let outputPath = outputPathArgument.first {
283285
var isDirectory: ObjCBool = false
284286
guard FileManager.default.fileExists(atPath: outputPath, isDirectory: &isDirectory), isDirectory.boolValue else {
285-
throw Errors.invalidArgument("invalid output directory \(outputPath)")
287+
throw Errors.invalidArgument("invalid output directory '\(outputPath)'")
286288
}
287289
self.outputDirectory = Path(outputPath)
288290
} else {
@@ -293,7 +295,7 @@ private struct Configuration: CustomStringConvertible {
293295
let products = try context.package.products(named: productsArgument)
294296
for product in products {
295297
guard product is ExecutableProduct else {
296-
throw Errors.invalidArgument("product named \(product.name) is not an executable product")
298+
throw Errors.invalidArgument("product named '\(product.name)' is not an executable product")
297299
}
298300
}
299301
self.products = products
@@ -304,7 +306,7 @@ private struct Configuration: CustomStringConvertible {
304306

305307
if let buildConfigurationName = configurationArgument.first {
306308
guard let buildConfiguration = PackageManager.BuildConfiguration(rawValue: buildConfigurationName) else {
307-
throw Errors.invalidArgument("invalid build configuration named \(buildConfigurationName)")
309+
throw Errors.invalidArgument("invalid build configuration named '\(buildConfigurationName)'")
308310
}
309311
self.buildConfiguration = buildConfiguration
310312
} else {
@@ -338,18 +340,6 @@ private struct Configuration: CustomStringConvertible {
338340
}
339341
}
340342

341-
private enum Errors: Error {
342-
case invalidArgument(String)
343-
case unsupportedPlatform(String)
344-
case unknownProduct(String)
345-
case unknownExecutable(String)
346-
case buildError(String)
347-
case productExecutableNotFound(String)
348-
case failedWritingDockerfile
349-
case processFailed(Int32)
350-
case invalidProcessOutput
351-
}
352-
353343
private enum ProcessLogLevel: Int, Comparable {
354344
case silent = 0
355345
case output = 1
@@ -360,21 +350,33 @@ private enum ProcessLogLevel: Int, Comparable {
360350
}
361351
}
362352

363-
extension PackageManager.BuildResult {
364-
// find the executable produced by the build
365-
func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? {
366-
let executables = self.builtArtifacts.filter { $0.kind == .executable && $0.path.lastComponent == product.name }
367-
guard !executables.isEmpty else {
368-
return nil
369-
}
370-
guard executables.count == 1, let executable = executables.first else {
371-
return nil
353+
private enum Errors: Error, CustomStringConvertible {
354+
case invalidArgument(String)
355+
case unsupportedPlatform(String)
356+
case unknownProduct(String)
357+
case productExecutableNotFound(String)
358+
case failedWritingDockerfile
359+
case processFailed([String], Int32)
360+
361+
var description: String {
362+
switch self {
363+
case .invalidArgument(let description):
364+
return description
365+
case .unsupportedPlatform(let description):
366+
return description
367+
case .unknownProduct(let description):
368+
return description
369+
case .productExecutableNotFound(let product):
370+
return "product executable not found '\(product)'"
371+
case .failedWritingDockerfile:
372+
return "failed writing dockerfile"
373+
case .processFailed(let arguments, let code):
374+
return "\(arguments.joined(separator: " ")) failed with code \(code)"
372375
}
373-
return executable
374376
}
375377
}
376378

377-
struct LambdaProduct: Hashable {
379+
private struct LambdaProduct: Hashable {
378380
let underlying: Product
379381

380382
init(_ underlying: Product) {
@@ -393,3 +395,17 @@ struct LambdaProduct: Hashable {
393395
lhs.underlying.id == rhs.underlying.id
394396
}
395397
}
398+
399+
extension PackageManager.BuildResult {
400+
// find the executable produced by the build
401+
func executableArtifact(for product: Product) -> PackageManager.BuildResult.BuiltArtifact? {
402+
let executables = self.builtArtifacts.filter { $0.kind == .executable && $0.path.lastComponent == product.name }
403+
guard !executables.isEmpty else {
404+
return nil
405+
}
406+
guard executables.count == 1, let executable = executables.first else {
407+
return nil
408+
}
409+
return executable
410+
}
411+
}

0 commit comments

Comments
 (0)