@@ -76,29 +76,104 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
76
76
stdoutStream <<< arguments. map { $0. spm_shellEscaped ( ) } . joined ( separator: " " ) <<< " \n "
77
77
stdoutStream. flush ( )
78
78
case . parsableOutput:
79
- let beganMessages = constructJobBeganMessages ( job: job, arguments: arguments, pid: pid)
80
- for beganMessage in beganMessages {
81
- let message = ParsableMessage ( name: job. kind. rawValue, kind: . began( beganMessage) )
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) ) )
82
+ }
83
+ }
84
+ }
85
+
86
+ public func jobFinished( job: Job , result: ProcessResult , pid: Int ) {
87
+ if showJobLifecycle {
88
+ diagnosticEngine. emit ( . remark_job_lifecycle( " Finished " , job) )
89
+ }
90
+
91
+ buildRecordInfo? . jobFinished ( job: job, result: result)
92
+
93
+ // FIXME: Currently, TSCBasic.Process uses NSProcess on Windows and discards
94
+ // the bits of the exit code used to differentiate between normal and abnormal
95
+ // termination.
96
+ #if !os(Windows)
97
+ if case . signalled = result. exitStatus {
98
+ anyJobHadAbnormalExit = true
99
+ }
100
+ #endif
101
+
102
+ switch mode {
103
+ case . regular, . verbose:
104
+ let output = ( try ? result. utf8Output ( ) + result. utf8stderrOutput ( ) ) ?? " "
105
+ if !output. isEmpty {
106
+ Driver . stdErrQueue. sync {
107
+ stderrStream <<< output
108
+ stderrStream. flush ( )
109
+ }
110
+ }
111
+
112
+ case . parsableOutput:
113
+ let output = ( try ? result. utf8Output ( ) + result. utf8stderrOutput ( ) ) . flatMap { $0. isEmpty ? nil : $0 }
114
+ let messages : [ ParsableMessage ]
115
+
116
+ switch result. exitStatus {
117
+ case . terminated( let code) :
118
+ messages = constructJobFinishedMessages ( job: job, exitCode: code, output: output,
119
+ pid: pid) . map {
120
+ ParsableMessage ( name: job. kind. rawValue, kind: . finished( $0) )
121
+ }
122
+ #if !os(Windows)
123
+ case . signalled( let signal) :
124
+ let errorMessage = strsignal ( signal) . map { String ( cString: $0) } ?? " "
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
+ }
129
+ #endif
130
+ }
131
+ for message in messages {
82
132
emit ( message)
83
133
}
84
134
}
85
135
}
86
136
87
- public func constructJobBeganMessages( job: Job , arguments: [ String ] , pid: Int ) -> [ BeganMessage ] {
137
+ public func jobSkipped( job: Job ) {
138
+ if showJobLifecycle {
139
+ diagnosticEngine. emit ( . remark_job_lifecycle( " Skipped " , job) )
140
+ }
141
+ switch mode {
142
+ case . regular, . verbose:
143
+ break
144
+ case . parsableOutput:
145
+ let skippedMessage = SkippedMessage ( inputs: job. displayInputs. map { $0. file. name } )
146
+ let message = ParsableMessage ( name: job. kind. rawValue, kind: . skipped( skippedMessage) )
147
+ emit ( message)
148
+ }
149
+ }
150
+
151
+ private func emit( _ message: ParsableMessage ) {
152
+ // FIXME: Do we need to do error handling here? Can this even fail?
153
+ guard let json = try ? message. toJSON ( ) else { return }
154
+ Driver . stdErrQueue. sync {
155
+ stderrStream <<< json. count <<< " \n "
156
+ stderrStream <<< String ( data: json, encoding: . utf8) ! <<< " \n "
157
+ stderrStream. flush ( )
158
+ }
159
+ }
160
+ }
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 ] {
88
168
let result : [ BeganMessage ]
89
- if job. kind == . compile {
90
- if job. primaryInputs. count == 1 {
91
- result = [ constructSingleBeganMessage ( inputs: job. displayInputs,
92
- outputs: job. outputs,
93
- arguments: arguments,
94
- pid: pid,
95
- realPid: pid) ]
96
- } else {
97
- // Batched compile jobs need to be broken up into multiple messages, one per constituent.
98
- result = constructBatchCompileBeginMessages ( job: job, arguments: arguments, pid: pid,
99
- quasiPIDBase: nextBatchQuasiPID)
100
- nextBatchQuasiPID -= result. count
101
- }
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
102
177
} else {
103
178
result = [ constructSingleBeganMessage ( inputs: job. displayInputs,
104
179
outputs: job. outputs,
@@ -110,8 +185,8 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
110
185
return result
111
186
}
112
187
113
- public func constructBatchCompileBeginMessages( job: Job , arguments: [ String ] ,
114
- pid : Int , quasiPIDBase: Int ) -> [ BeganMessage ] {
188
+ func constructBatchCompileBeginMessages( job: Job , arguments: [ String ] , pid : Int ,
189
+ quasiPIDBase: Int ) -> [ BeganMessage ] {
115
190
precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
116
191
var quasiPID = quasiPIDBase
117
192
var result : [ BeganMessage ] = [ ]
@@ -137,12 +212,8 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
137
212
return result
138
213
}
139
214
140
- public func constructSingleBeganMessage( inputs: [ TypedVirtualPath ] ,
141
- outputs: [ TypedVirtualPath ] ,
142
- arguments: [ String ] ,
143
- pid: Int ,
144
- realPid: Int ) -> BeganMessage {
145
-
215
+ func constructSingleBeganMessage( inputs: [ TypedVirtualPath ] , outputs: [ TypedVirtualPath ] ,
216
+ arguments: [ String ] , pid: Int , realPid: Int ) -> BeganMessage {
146
217
let outputs : [ BeganMessage . Output ] = outputs. map {
147
218
. init( path: $0. file. name, type: $0. type. description)
148
219
}
@@ -157,10 +228,82 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
157
228
)
158
229
}
159
230
231
+ // MARK: - Job Finished
232
+ func constructJobFinishedMessages( job: Job , exitCode: Int32 , output: String ? , pid: Int )
233
+ -> [ FinishedMessage ] {
234
+ let result : [ FinishedMessage ]
235
+ if job. kind == . compile,
236
+ job. primaryInputs. count > 1 {
237
+ result = constructBatchCompileFinishedMessages ( job: job, exitCode: exitCode,
238
+ output: output, pid: pid)
239
+ } else {
240
+ result = [ constructSingleFinishedMessage ( exitCode: exitCode, output: output,
241
+ pid: pid, realPid: pid) ]
242
+ }
243
+ return result
244
+ }
245
+
246
+ func constructBatchCompileFinishedMessages( job: Job , exitCode: Int32 , output: String ? , pid: Int )
247
+ -> [ FinishedMessage ] {
248
+ precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
249
+ var result : [ FinishedMessage ] = [ ]
250
+ for input in job. primaryInputs {
251
+ guard let quasiPid = batchJobInputQuasiPIDMap [ ( job, input) ] else {
252
+ fatalError ( " Parsable-Output batch sub-job finished with no matching started message: \( job. description) : \( input. file. description) " )
253
+ }
254
+ result. append (
255
+ constructSingleFinishedMessage ( exitCode: exitCode, output: output,
256
+ pid: quasiPid, realPid: pid) )
257
+ }
258
+ return result
259
+ }
260
+
261
+ func constructSingleFinishedMessage( exitCode: Int32 , output: String ? , pid: Int , realPid: Int )
262
+ -> FinishedMessage {
263
+ return FinishedMessage ( exitStatus: Int ( exitCode) , output: output, pid: pid, realPid: realPid)
264
+ }
265
+
266
+ // MARK: - Job Signalled
267
+ func constructJobSignalledMessages( job: Job , error: String , output: String ? ,
268
+ signal: Int32 , pid: Int ) -> [ SignalledMessage ] {
269
+ let result : [ SignalledMessage ]
270
+ if job. kind == . compile,
271
+ job. primaryInputs. count > 1 {
272
+ result = constructBatchCompileSignalledMessages ( job: job, error: error, output: output,
273
+ signal: signal, pid: pid)
274
+ } else {
275
+ result = [ constructSingleSignalledMessage ( error: error, output: output, signal: signal,
276
+ pid: pid, realPid: pid) ]
277
+ }
278
+ return result
279
+ }
280
+
281
+ func constructBatchCompileSignalledMessages( job: Job , error: String , output: String ? ,
282
+ signal: Int32 , pid: Int )
283
+ -> [ SignalledMessage ] {
284
+ precondition ( job. kind == . compile && job. primaryInputs. count > 1 )
285
+ var result : [ SignalledMessage ] = [ ]
286
+ for input in job. primaryInputs {
287
+ guard let quasiPid = batchJobInputQuasiPIDMap [ ( job, input) ] else {
288
+ fatalError ( " Parsable-Output batch sub-job signalled with no matching started message: \( job. description) : \( input. file. description) " )
289
+ }
290
+ result. append (
291
+ constructSingleSignalledMessage ( error: error, output: output, signal: signal,
292
+ pid: quasiPid, realPid: pid) )
293
+ }
294
+ return result
295
+ }
296
+
297
+ func constructSingleSignalledMessage( error: String , output: String ? , signal: Int32 ,
298
+ pid: Int , realPid: Int )
299
+ -> SignalledMessage {
300
+ return SignalledMessage ( pid: pid, realPid: realPid, output: output,
301
+ errorMessage: error, signal: Int ( signal) )
302
+ }
160
303
161
304
/// Best-effort attempt to "fix-up" the individual swift-frontend invocation command line, to pretend
162
305
/// it is an individual single-primary compile job, rather than a batch mode compile with multiple primaries
163
- private static func filterPrimaryArguments( in arguments: [ String ] ,
306
+ static func filterPrimaryArguments( in arguments: [ String ] ,
164
307
input: TypedVirtualPath ,
165
308
outputs: [ TypedVirtualPath ] ) -> [ String ] {
166
309
// We must have only one `-primary-file` option specified, the one that corresponds
@@ -195,76 +338,6 @@ final class ToolExecutionDelegate: JobExecutionDelegate {
195
338
196
339
return result
197
340
}
198
-
199
- public func jobFinished( job: Job , result: ProcessResult , pid: Int ) {
200
- if showJobLifecycle {
201
- diagnosticEngine. emit ( . remark_job_lifecycle( " Finished " , job) )
202
- }
203
-
204
- buildRecordInfo? . jobFinished ( job: job, result: result)
205
-
206
- // FIXME: Currently, TSCBasic.Process uses NSProcess on Windows and discards
207
- // the bits of the exit code used to differentiate between normal and abnormal
208
- // termination.
209
- #if !os(Windows)
210
- if case . signalled = result. exitStatus {
211
- anyJobHadAbnormalExit = true
212
- }
213
- #endif
214
-
215
- switch mode {
216
- case . regular, . verbose:
217
- let output = ( try ? result. utf8Output ( ) + result. utf8stderrOutput ( ) ) ?? " "
218
- if !output. isEmpty {
219
- Driver . stdErrQueue. sync {
220
- stderrStream <<< output
221
- stderrStream. flush ( )
222
- }
223
- }
224
-
225
- case . parsableOutput:
226
- let output = ( try ? result. utf8Output ( ) + result. utf8stderrOutput ( ) ) . flatMap { $0. isEmpty ? nil : $0 }
227
- let message : ParsableMessage
228
-
229
- switch result. exitStatus {
230
- case . terminated( let code) :
231
- let finishedMessage = FinishedMessage ( exitStatus: Int ( code) , pid: pid, output: output)
232
- message = ParsableMessage ( name: job. kind. rawValue, kind: . finished( finishedMessage) )
233
-
234
- #if !os(Windows)
235
- case . signalled( let signal) :
236
- let errorMessage = strsignal ( signal) . map { String ( cString: $0) } ?? " "
237
- let signalledMessage = SignalledMessage ( pid: pid, output: output, errorMessage: errorMessage, signal: Int ( signal) )
238
- message = ParsableMessage ( name: job. kind. rawValue, kind: . signalled( signalledMessage) )
239
- #endif
240
- }
241
- emit ( message)
242
- }
243
- }
244
-
245
- public func jobSkipped( job: Job ) {
246
- if showJobLifecycle {
247
- diagnosticEngine. emit ( . remark_job_lifecycle( " Skipped " , job) )
248
- }
249
- switch mode {
250
- case . regular, . verbose:
251
- break
252
- case . parsableOutput:
253
- let skippedMessage = SkippedMessage ( inputs: job. displayInputs. map { $0. file. name } )
254
- let message = ParsableMessage ( name: job. kind. rawValue, kind: . skipped( skippedMessage) )
255
- emit ( message)
256
- }
257
- }
258
-
259
- private func emit( _ message: ParsableMessage ) {
260
- // FIXME: Do we need to do error handling here? Can this even fail?
261
- guard let json = try ? message. toJSON ( ) else { return }
262
- Driver . stdErrQueue. sync {
263
- stderrStream <<< json. count <<< " \n "
264
- stderrStream <<< String ( data: json, encoding: . utf8) ! <<< " \n "
265
- stderrStream. flush ( )
266
- }
267
- }
268
341
}
269
342
270
343
fileprivate extension Diagnostic . Message {
0 commit comments