1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
//===--------------- DriverExecutor.swift - Swift Driver Executor----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import struct TSCBasic.ProcessResult
import struct Foundation.Data
import class Foundation.JSONDecoder
import var Foundation.EXIT_SUCCESS
/// A type that is capable of executing compilation jobs on some underlying
/// build service.
public protocol DriverExecutor {
var resolver: ArgsResolver { get }
/// Execute a single job and capture the output.
@discardableResult
func execute(job: Job,
forceResponseFiles: Bool,
recordedInputModificationDates: [TypedVirtualPath: TimePoint]) throws -> ProcessResult
/// Execute multiple jobs, tracking job status using the provided execution delegate.
/// Pass in the `IncrementalCompilationState` to allow for incremental compilation.
/// Pass in the `InterModuleDependencyGraph` to allow for module dependency tracking.
func execute(workload: DriverExecutorWorkload,
delegate: JobExecutionDelegate,
numParallelJobs: Int,
forceResponseFiles: Bool,
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
) throws
/// Execute multiple jobs, tracking job status using the provided execution delegate.
func execute(jobs: [Job],
delegate: JobExecutionDelegate,
numParallelJobs: Int,
forceResponseFiles: Bool,
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
) throws
/// Launch a process with the given command line and report the result.
@discardableResult
func checkNonZeroExit(args: String..., environment: [String: String]) throws -> String
/// Returns a textual description of the job as it would be run by the executor.
func description(of job: Job, forceResponseFiles: Bool) throws -> String
}
public struct DriverExecutorWorkload {
public let continueBuildingAfterErrors: Bool
public enum Kind {
case all([Job])
case incremental(IncrementalCompilationState)
}
public let kind: Kind
public let interModuleDependencyGraph: InterModuleDependencyGraph?
public init(_ allJobs: [Job],
_ incrementalCompilationState: IncrementalCompilationState?,
_ interModuleDependencyGraph: InterModuleDependencyGraph?,
continueBuildingAfterErrors: Bool) {
self.continueBuildingAfterErrors = continueBuildingAfterErrors
self.kind = incrementalCompilationState
.map {.incremental($0)}
?? .all(allJobs)
self.interModuleDependencyGraph = interModuleDependencyGraph
}
static public func all(_ jobs: [Job],
_ interModuleDependencyGraph: InterModuleDependencyGraph? = nil) -> Self {
.init(jobs, nil, interModuleDependencyGraph, continueBuildingAfterErrors: false)
}
}
enum JobExecutionError: Error {
case jobFailedWithNonzeroExitCode(Int, String)
case failedToReadJobOutput
// A way to pass more information to the catch point
case decodingError(DecodingError, Data, ProcessResult)
}
extension DriverExecutor {
func execute<T: Decodable>(job: Job,
capturingJSONOutputAs outputType: T.Type,
forceResponseFiles: Bool,
recordedInputModificationDates: [TypedVirtualPath: TimePoint]) throws -> T {
let result = try execute(job: job,
forceResponseFiles: forceResponseFiles,
recordedInputModificationDates: recordedInputModificationDates)
if (result.exitStatus != .terminated(code: EXIT_SUCCESS)) {
let returnCode = Self.computeReturnCode(exitStatus: result.exitStatus)
throw JobExecutionError.jobFailedWithNonzeroExitCode(returnCode, try result.utf8stderrOutput())
}
guard let outputData = try? Data(result.utf8Output().utf8) else {
throw JobExecutionError.failedToReadJobOutput
}
do {
return try JSONDecoder().decode(outputType, from: outputData)
} catch let err as DecodingError {
throw JobExecutionError.decodingError(err, outputData, result)
}
}
public func execute(
jobs: [Job],
delegate: JobExecutionDelegate,
numParallelJobs: Int,
forceResponseFiles: Bool,
recordedInputModificationDates: [TypedVirtualPath: TimePoint]
) throws {
try execute(
workload: .all(jobs, nil),
delegate: delegate,
numParallelJobs: numParallelJobs,
forceResponseFiles: forceResponseFiles,
recordedInputModificationDates: recordedInputModificationDates)
}
static func computeReturnCode(exitStatus: ProcessResult.ExitStatus) -> Int {
var returnCode: Int
switch exitStatus {
case .terminated(let code):
returnCode = Int(code)
#if os(Windows)
case .abnormal(let exception):
returnCode = Int(exception)
#else
case .signalled(let signal):
returnCode = Int(signal)
#endif
}
return returnCode
}
}
public protocol JobExecutionDelegate {
/// Called when a job starts executing.
func jobStarted(job: Job, arguments: [String], pid: Int)
/// Called when a job finished.
func jobFinished(job: Job, result: ProcessResult, pid: Int)
/// Called when a job is skipped.
func jobSkipped(job: Job)
}
|