@@ -23,7 +23,16 @@ import Glibc
23
23
#endif
24
24
25
25
/// Delegate for printing execution information on the command-line.
26
- final class ToolExecutionDelegate : JobExecutionDelegate {
26
+ @_spi ( Testing) public final class ToolExecutionDelegate : JobExecutionDelegate {
27
+ /// Quasi-PIDs are _negative_ PID-like unique keys used to
28
+ /// masquerade batch job constituents as (quasi)processes, when writing
29
+ /// parseable output to consumers that don't understand the idea of a batch
30
+ /// job. They are negative in order to avoid possibly colliding with real
31
+ /// PIDs (which are always positive). We start at -1000 here as a crude but
32
+ /// harmless hedge against colliding with an errno value that might slip
33
+ /// into the stream of real PIDs.
34
+ static let QUASI_PID_START = - 1000
35
+
27
36
public enum Mode {
28
37
case verbose
29
38
case parsableOutput
@@ -35,19 +44,25 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
35
44
public let incrementalCompilationState : IncrementalCompilationState ?
36
45
public let showJobLifecycle : Bool
37
46
public let diagnosticEngine : DiagnosticsEngine
38
-
39
47
public var anyJobHadAbnormalExit : Bool = false
40
48
41
- init ( mode: ToolExecutionDelegate . Mode ,
42
- buildRecordInfo: BuildRecordInfo ? ,
43
- incrementalCompilationState: IncrementalCompilationState ? ,
44
- showJobLifecycle: Bool ,
45
- diagnosticEngine: DiagnosticsEngine ) {
49
+ private var nextBatchQuasiPID : Int
50
+ private let argsResolver : ArgsResolver
51
+ private var batchJobInputQuasiPIDMap = DictionaryOfDictionaries < Job , TypedVirtualPath , Int > ( )
52
+
53
+ @_spi ( Testing) public init ( mode: ToolExecutionDelegate . Mode ,
54
+ buildRecordInfo: BuildRecordInfo ? ,
55
+ incrementalCompilationState: IncrementalCompilationState ? ,
56
+ showJobLifecycle: Bool ,
57
+ argsResolver: ArgsResolver ,
58
+ diagnosticEngine: DiagnosticsEngine ) {
46
59
self . mode = mode
47
60
self . buildRecordInfo = buildRecordInfo
48
61
self . incrementalCompilationState = incrementalCompilationState
49
62
self . showJobLifecycle = showJobLifecycle
50
63
self . diagnosticEngine = diagnosticEngine
64
+ self . argsResolver = argsResolver
65
+ self . nextBatchQuasiPID = ToolExecutionDelegate . QUASI_PID_START
51
66
}
52
67
53
68
public func jobStarted( job: Job , arguments: [ String ] , pid: Int ) {
@@ -61,22 +76,10 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
61
76
stdoutStream <<< arguments. map { $0. spm_shellEscaped ( ) } . joined ( separator: " " ) <<< " \n "
62
77
stdoutStream. flush ( )
63
78
case . parsableOutput:
64
-
65
- // Compute the outputs for the message.
66
- let outputs : [ BeganMessage . Output ] = job. outputs. map {
67
- . init( path: $0. file. name, type: $0. type. description)
79
+ let messages = constructJobBeganMessages ( job: job, arguments: arguments, pid: pid)
80
+ for beganMessage in messages {
81
+ emit ( ParsableMessage ( name: job. kind. rawValue, kind: . began( beganMessage) ) )
68
82
}
69
-
70
- let beganMessage = BeganMessage (
71
- pid: pid,
72
- inputs: job. displayInputs. map { $0. file. name } ,
73
- outputs: outputs,
74
- commandExecutable: arguments [ 0 ] ,
75
- commandArguments: arguments [ 1 ... ] . map { String ( $0) }
76
- )
77
-
78
- let message = ParsableMessage ( name: job. kind. rawValue, kind: . began( beganMessage) )
79
- emit ( message)
80
83
}
81
84
}
82
85
@@ -108,21 +111,26 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
108
111
109
112
case . parsableOutput:
110
113
let output = ( try ? result. utf8Output ( ) + result. utf8stderrOutput ( ) ) . flatMap { $0. isEmpty ? nil : $0 }
111
- let message : ParsableMessage
114
+ let messages : [ ParsableMessage ]
112
115
113
116
switch result. exitStatus {
114
117
case . terminated( let code) :
115
- let finishedMessage = FinishedMessage ( exitStatus: Int ( code) , pid: pid, output: output)
116
- message = ParsableMessage ( name: job. kind. rawValue, kind: . finished( finishedMessage) )
117
-
118
+ messages = constructJobFinishedMessages ( job: job, exitCode: code, output: output,
119
+ pid: pid) . map {
120
+ ParsableMessage ( name: job. kind. rawValue, kind: . finished( $0) )
121
+ }
118
122
#if !os(Windows)
119
123
case . signalled( let signal) :
120
124
let errorMessage = strsignal ( signal) . map { String ( cString: $0) } ?? " "
121
- let signalledMessage = SignalledMessage ( pid: pid, output: output, errorMessage: errorMessage, signal: Int ( signal) )
122
- message = ParsableMessage ( name: job. kind. rawValue, kind: . signalled( signalledMessage) )
125
+ messages = constructJobSignalledMessages ( job: job, error: errorMessage, output: output,
126
+ signal: signal, pid: pid) . map {
127
+ ParsableMessage ( name: job. kind. rawValue, kind: . signalled( $0) )
128
+ }
123
129
#endif
124
130
}
125
- emit ( message)
131
+ for message in messages {
132
+ emit ( message)
133
+ }
126
134
}
127
135
}
128
136
@@ -151,6 +159,147 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
151
159
}
152
160
}
153
161
162
+ // MARK: - Message Construction
163
+ /// Generation of messages from jobs, including breaking down batch compile jobs into constituent messages.
164
+ private extension ToolExecutionDelegate {
165
+
166
+ // MARK: - Job Began
167
+ func constructJobBeganMessages( job: Job , arguments: [ String ] , pid: Int ) -> [ BeganMessage ] {
168
+ let result : [ BeganMessage ]
169
+ if job. kind == . compile,
170
+ job. primaryInputs. count > 1 {
171
+ // Batched compile jobs need to be broken up into multiple messages, one per constituent.
172
+ result = constructBatchCompileBeginMessages ( job: job, arguments: arguments, pid: pid,
173
+ quasiPIDBase: nextBatchQuasiPID)
174
+ // Today, parseable-output messages are constructed and emitted synchronously
175
+ // on `MultiJobExecutor`'s `delegateQueue`. This is why the below operation is safe.
176
+ nextBatchQuasiPID -= result. count
177
+ } else {
178
+ result = [ constructSingleBeganMessage ( inputs: job. displayInputs,
179
+ outputs: job. outputs,
180
+ arguments: arguments,
181
+ pid: pid,
182
+ realPid: pid) ]
183
+ }
184
+
185
+ return result
186
+ }
187
+
188
+ func constructBatchCompileBeginMessages( job: Job , arguments: [ String ] , pid: Int ,
189
+ quasiPIDBase: Int ) -> [ BeganMessage ] {
190
+ precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
191
+ var quasiPID = quasiPIDBase
192
+ var result : [ BeganMessage ] = [ ]
193
+ for input in job. primaryInputs {
194
+ let outputs = job. getCompileInputOutputs ( for: input) ?? [ ]
195
+ let outputPaths = outputs. map {
196
+ TypedVirtualPath ( file: try ! VirtualPath . intern ( path: argsResolver. resolve ( . path( $0. file) ) ) ,
197
+ type: $0. type)
198
+ }
199
+ result. append (
200
+ constructSingleBeganMessage ( inputs: [ input] ,
201
+ outputs: outputPaths,
202
+ arguments: arguments,
203
+ pid: quasiPID,
204
+ realPid: pid) )
205
+ // Save the quasiPID of this job/input combination in order to generate the correct
206
+ // `finished` message
207
+ batchJobInputQuasiPIDMap [ ( job, input) ] = quasiPID
208
+ quasiPID -= 1
209
+ }
210
+ return result
211
+ }
212
+
213
+ func constructSingleBeganMessage( inputs: [ TypedVirtualPath ] , outputs: [ TypedVirtualPath ] ,
214
+ arguments: [ String ] , pid: Int , realPid: Int ) -> BeganMessage {
215
+ let outputs : [ BeganMessage . Output ] = outputs. map {
216
+ . init( path: $0. file. name, type: $0. type. description)
217
+ }
218
+
219
+ return BeganMessage (
220
+ pid: pid,
221
+ realPid: realPid,
222
+ inputs: inputs. map { $0. file. name } ,
223
+ outputs: outputs,
224
+ commandExecutable: arguments [ 0 ] ,
225
+ commandArguments: arguments [ 1 ... ] . map { String ( $0) }
226
+ )
227
+ }
228
+
229
+ // MARK: - Job Finished
230
+ func constructJobFinishedMessages( job: Job , exitCode: Int32 , output: String ? , pid: Int )
231
+ -> [ FinishedMessage ] {
232
+ let result : [ FinishedMessage ]
233
+ if job. kind == . compile,
234
+ job. primaryInputs. count > 1 {
235
+ result = constructBatchCompileFinishedMessages ( job: job, exitCode: exitCode,
236
+ output: output, pid: pid)
237
+ } else {
238
+ result = [ constructSingleFinishedMessage ( exitCode: exitCode, output: output,
239
+ pid: pid, realPid: pid) ]
240
+ }
241
+ return result
242
+ }
243
+
244
+ func constructBatchCompileFinishedMessages( job: Job , exitCode: Int32 , output: String ? , pid: Int )
245
+ -> [ FinishedMessage ] {
246
+ precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
247
+ var result : [ FinishedMessage ] = [ ]
248
+ for input in job. primaryInputs {
249
+ guard let quasiPid = batchJobInputQuasiPIDMap [ ( job, input) ] else {
250
+ fatalError ( " Parsable-Output batch sub-job finished with no matching started message: \( job. description) : \( input. file. description) " )
251
+ }
252
+ result. append (
253
+ constructSingleFinishedMessage ( exitCode: exitCode, output: output,
254
+ pid: quasiPid, realPid: pid) )
255
+ }
256
+ return result
257
+ }
258
+
259
+ func constructSingleFinishedMessage( exitCode: Int32 , output: String ? , pid: Int , realPid: Int )
260
+ -> FinishedMessage {
261
+ return FinishedMessage ( exitStatus: Int ( exitCode) , output: output, pid: pid, realPid: realPid)
262
+ }
263
+
264
+ // MARK: - Job Signalled
265
+ func constructJobSignalledMessages( job: Job , error: String , output: String ? ,
266
+ signal: Int32 , pid: Int ) -> [ SignalledMessage ] {
267
+ let result : [ SignalledMessage ]
268
+ if job. kind == . compile,
269
+ job. primaryInputs. count > 1 {
270
+ result = constructBatchCompileSignalledMessages ( job: job, error: error, output: output,
271
+ signal: signal, pid: pid)
272
+ } else {
273
+ result = [ constructSingleSignalledMessage ( error: error, output: output, signal: signal,
274
+ pid: pid, realPid: pid) ]
275
+ }
276
+ return result
277
+ }
278
+
279
+ func constructBatchCompileSignalledMessages( job: Job , error: String , output: String ? ,
280
+ signal: Int32 , pid: Int )
281
+ -> [ SignalledMessage ] {
282
+ precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
283
+ var result : [ SignalledMessage ] = [ ]
284
+ for input in job. primaryInputs {
285
+ guard let quasiPid = batchJobInputQuasiPIDMap [ ( job, input) ] else {
286
+ fatalError ( " Parsable-Output batch sub-job signalled with no matching started message: \( job. description) : \( input. file. description) " )
287
+ }
288
+ result. append (
289
+ constructSingleSignalledMessage ( error: error, output: output, signal: signal,
290
+ pid: quasiPid, realPid: pid) )
291
+ }
292
+ return result
293
+ }
294
+
295
+ func constructSingleSignalledMessage( error: String , output: String ? , signal: Int32 ,
296
+ pid: Int , realPid: Int )
297
+ -> SignalledMessage {
298
+ return SignalledMessage ( pid: pid, realPid: realPid, output: output,
299
+ errorMessage: error, signal: Int ( signal) )
300
+ }
301
+ }
302
+
154
303
fileprivate extension Diagnostic . Message {
155
304
static func remark_job_lifecycle( _ what: String , _ job: Job
156
305
) -> Diagnostic . Message {
0 commit comments