diff --git a/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift b/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift index 5a3332b0..5b7a5685 100644 --- a/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift +++ b/Sources/SWBCore/LibSwiftDriver/PlannedBuild.swift @@ -529,6 +529,16 @@ extension LibSwiftDriver { } } + public func getCrashReproducerCommand(for job: PlannedSwiftDriverJob, output dir: Path) async throws -> [String]? { + try await dispatchQueue.sync { + let driverJob = try self.driverJob(for: job) + guard let reproJob = self.jobExecutionDelegate?.getReproducerJob(job: driverJob, output: try VirtualPath(path: dir.str)) else { + return nil + } + return try self.argsResolver.resolveArgumentList(for: reproJob, useResponseFiles: .heuristic) + } + } + public func getDiscoveredJobsAfterFinishing(job: PlannedSwiftDriverJob) throws -> [PlannedSwiftDriverJob] { dispatchQueue.blocking_sync { diff --git a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift index 7851c42d..2ed927e5 100644 --- a/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/SwiftDriverJobTaskAction.swift @@ -420,6 +420,7 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas private var pid = llbuild_pid_t.invalid var executionError: String? + var wasSignaled: Bool = false private var processStarted = false private var _commandResult: CommandResult? var commandResult: CommandResult? { @@ -469,10 +470,15 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas } func processFinished(result: CommandExtendedResult) { + if wasSignaled { + // If the process was already signaled, this might be in a reproducer creation. No need to update finish status. + return + } guard let status = Processes.ExitStatus.init(rawValue: result.exitStatus) else { // nil means the job is stopped or continued. It should not call finished. return } + wasSignaled = status.wasSignaled // This may be updated by commandStarted in the case of certain failures, // so only update the exit status in output delegate if it is nil. if outputDelegate.result == nil { @@ -526,6 +532,21 @@ public final class SwiftDriverJobTaskAction: TaskAction, BuildValueValidatingTas try await spawn(commandLine: options.commandLine, environment: environment, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) + // Generate crash reproducoer. + if delegate.wasSignaled { + // The output directory for crash reproducer is: + // * Specified by environment + // * Primary output path directory + // * Temp directory + let reproDir = environment["SWIFT_CRASH_DIAGNOSTICS_DIR"].map(Path.init) ?? driverJob.driverJob.outputs.first?.dirname + try await withTemporaryDirectory(dir: reproDir, prefix: "swift-crash-reproducer", removeTreeOnDeinit: false) { dir in + if let reproCommand = try await plannedBuild?.getCrashReproducerCommand(for: driverJob, output: dir) { + try await spawn(commandLine: reproCommand, environment: environment, workingDirectory: task.workingDirectory, dynamicExecutionDelegate: dynamicExecutionDelegate, clientDelegate: clientDelegate, processDelegate: delegate) + outputDelegate.note("Crash reproducer created in \(dir.str)") + } + } + } + if let error = delegate.executionError { outputDelegate.error(error) return .failed