@@ -204,11 +204,64 @@ public final class Process {
204
204
/// Typealias for stdout/stderr output closure.
205
205
public typealias OutputClosure = ( [ UInt8 ] ) -> Void
206
206
207
- /// Global default setting for verbose.
208
- public static var verbose = false
207
+ /// Typealias for logging handling closure
208
+ public typealias LoggingHandler = ( String ) -> Void
209
209
210
- /// If true, prints the subprocess arguments before launching it.
211
- public let verbose : Bool
210
+ private static var _loggingHandler : LoggingHandler ?
211
+ private static let loggingHandlerLock = Lock ( )
212
+
213
+ /// Global logging handler. Use with care! preferably use instance level instead of setting one globally.
214
+ public static var loggingHandler : LoggingHandler ? {
215
+ get {
216
+ Self . loggingHandlerLock. withLock {
217
+ self . _loggingHandler
218
+ }
219
+ } set {
220
+ Self . loggingHandlerLock. withLock {
221
+ self . _loggingHandler = newValue
222
+ }
223
+ }
224
+ }
225
+
226
+ // deprecated 2/2022, remove once client migrate to logging handler
227
+ @available ( * , deprecated)
228
+ public static var verbose : Bool {
229
+ get {
230
+ Self . loggingHandler != nil
231
+ } set {
232
+ Self . loggingHandler = newValue ? Self . logToStdout: . none
233
+ }
234
+ }
235
+
236
+ private var _loggingHandler : LoggingHandler ?
237
+
238
+ // the log and setter are only required to backward support verbose setter.
239
+ // remove and make loggingHandler a let property once verbose is deprecated
240
+ private let loggingHandlerLock = Lock ( )
241
+ public private( set) var loggingHandler : LoggingHandler ? {
242
+ get {
243
+ self . loggingHandlerLock. withLock {
244
+ self . _loggingHandler
245
+ }
246
+ }
247
+ set {
248
+ self . loggingHandlerLock. withLock {
249
+ self . _loggingHandler = newValue
250
+ }
251
+ }
252
+ }
253
+
254
+ // deprecated 2/2022, remove once client migrate to logging handler
255
+ // also simplify loggingHandler (see above) once this is removed
256
+ @available ( * , deprecated)
257
+ public var verbose : Bool {
258
+ get {
259
+ self . loggingHandler != nil
260
+ }
261
+ set {
262
+ self . loggingHandler = newValue ? Self . logToStdout : . none
263
+ }
264
+ }
212
265
213
266
/// The current environment.
214
267
@available ( * , deprecated, message: " use ProcessEnv.vars instead " )
@@ -238,6 +291,7 @@ public final class Process {
238
291
// process execution mutable state
239
292
private var state : State = . idle
240
293
private let stateLock = Lock ( )
294
+
241
295
private static let sharedCompletionQueue = DispatchQueue ( label: " org.swift.tools-support-core.process-completion " )
242
296
private var completionQueue = Process . sharedCompletionQueue
243
297
@@ -286,24 +340,50 @@ public final class Process {
286
340
/// will be inherited.
287
341
/// - workingDirectory: The path to the directory under which to run the process.
288
342
/// - outputRedirection: How process redirects its output. Default value is .collect.
289
- /// - verbose: If true, launch() will print the arguments of the subprocess before launching it.
290
343
/// - startNewProcessGroup: If true, a new progress group is created for the child making it
291
344
/// continue running even if the parent is killed or interrupted. Default value is true.
345
+ /// - loggingHandler: Handler for logging messages
346
+ ///
292
347
@available ( macOS 10 . 15 , * )
293
348
public init (
294
349
arguments: [ String ] ,
295
350
environment: [ String : String ] = ProcessEnv . vars,
296
351
workingDirectory: AbsolutePath ,
297
352
outputRedirection: OutputRedirection = . collect,
298
- verbose : Bool = Process . verbose ,
299
- startNewProcessGroup : Bool = true
353
+ startNewProcessGroup : Bool = true ,
354
+ loggingHandler : LoggingHandler ? = . none
300
355
) {
301
356
self . arguments = arguments
302
357
self . environment = environment
303
358
self . workingDirectory = workingDirectory
304
359
self . outputRedirection = outputRedirection
305
- self . verbose = verbose
306
360
self . startNewProcessGroup = startNewProcessGroup
361
+ self . loggingHandler = loggingHandler ?? Process . loggingHandler
362
+ }
363
+
364
+ // deprecated 2/2022
365
+ @_disfavoredOverload
366
+ @available ( * , deprecated, message: " use version without verbosity flag " )
367
+ @available ( macOS 10 . 15 , * )
368
+ public convenience init (
369
+ arguments: [ String ] ,
370
+ environment: [ String : String ] = ProcessEnv . vars,
371
+ workingDirectory: AbsolutePath ,
372
+ outputRedirection: OutputRedirection = . collect,
373
+ verbose: Bool ,
374
+ startNewProcessGroup: Bool = true
375
+ ) {
376
+ self . init (
377
+ arguments: arguments,
378
+ environment: environment,
379
+ workingDirectory: workingDirectory,
380
+ outputRedirection: outputRedirection,
381
+ startNewProcessGroup: startNewProcessGroup,
382
+ loggingHandler: verbose ? { message in
383
+ stdoutStream <<< message <<< " \n "
384
+ stdoutStream. flush ( )
385
+ } : nil
386
+ )
307
387
}
308
388
309
389
/// Create a new process instance.
@@ -316,19 +396,52 @@ public final class Process {
316
396
/// - verbose: If true, launch() will print the arguments of the subprocess before launching it.
317
397
/// - startNewProcessGroup: If true, a new progress group is created for the child making it
318
398
/// continue running even if the parent is killed or interrupted. Default value is true.
399
+ /// - loggingHandler: Handler for logging messages
319
400
public init (
320
401
arguments: [ String ] ,
321
402
environment: [ String : String ] = ProcessEnv . vars,
322
403
outputRedirection: OutputRedirection = . collect,
323
- verbose : Bool = Process . verbose ,
324
- startNewProcessGroup : Bool = true
404
+ startNewProcessGroup : Bool = true ,
405
+ loggingHandler : LoggingHandler ? = . none
325
406
) {
326
407
self . arguments = arguments
327
408
self . environment = environment
328
409
self . workingDirectory = nil
329
410
self . outputRedirection = outputRedirection
330
- self . verbose = verbose
331
411
self . startNewProcessGroup = startNewProcessGroup
412
+ self . loggingHandler = loggingHandler ?? Process . loggingHandler
413
+ }
414
+
415
+ @_disfavoredOverload
416
+ @available ( * , deprecated, message: " user version without verbosity flag " )
417
+ public convenience init (
418
+ arguments: [ String ] ,
419
+ environment: [ String : String ] = ProcessEnv . vars,
420
+ outputRedirection: OutputRedirection = . collect,
421
+ verbose: Bool = Process . verbose,
422
+ startNewProcessGroup: Bool = true
423
+ ) {
424
+ self . init (
425
+ arguments: arguments,
426
+ environment: environment,
427
+ outputRedirection: outputRedirection,
428
+ startNewProcessGroup: startNewProcessGroup,
429
+ loggingHandler: verbose ? Self . logToStdout : . none
430
+ )
431
+ }
432
+
433
+ public convenience init (
434
+ args: String ... ,
435
+ environment: [ String : String ] = ProcessEnv . vars,
436
+ outputRedirection: OutputRedirection = . collect,
437
+ loggingHandler: LoggingHandler ? = . none
438
+ ) {
439
+ self . init (
440
+ arguments: args,
441
+ environment: environment,
442
+ outputRedirection: outputRedirection,
443
+ loggingHandler: loggingHandler
444
+ )
332
445
}
333
446
334
447
/// Returns the path of the the given program if found in the search paths.
@@ -393,9 +506,8 @@ public final class Process {
393
506
}
394
507
395
508
// Print the arguments if we are verbose.
396
- if self . verbose {
397
- stdoutStream <<< arguments. map ( { $0. spm_shellEscaped ( ) } ) . joined ( separator: " " ) <<< " \n "
398
- stdoutStream. flush ( )
509
+ if let loggingHandler = self . loggingHandler {
510
+ loggingHandler ( arguments. map ( { $0. spm_shellEscaped ( ) } ) . joined ( separator: " " ) )
399
511
}
400
512
401
513
// Look for executable.
@@ -832,11 +944,23 @@ extension Process {
832
944
/// - arguments: The arguments for the subprocess.
833
945
/// - environment: The environment to pass to subprocess. By default the current process environment
834
946
/// will be inherited.
835
- /// - Returns: The process result.
836
- static public func popen( arguments: [ String ] , environment: [ String : String ] = ProcessEnv . vars,
837
- queue: DispatchQueue ? = nil , completion: @escaping ( Result < ProcessResult , Swift . Error > ) -> Void ) {
947
+ /// - loggingHandler: Handler for logging messages
948
+ /// - queue: Queue to use for callbacks
949
+ /// - completion: A completion handler to return the process result
950
+ static public func popen(
951
+ arguments: [ String ] ,
952
+ environment: [ String : String ] = ProcessEnv . vars,
953
+ loggingHandler: LoggingHandler ? = . none,
954
+ queue: DispatchQueue ? = nil ,
955
+ completion: @escaping ( Result < ProcessResult , Swift . Error > ) -> Void
956
+ ) {
838
957
do {
839
- let process = Process ( arguments: arguments, environment: environment, outputRedirection: . collect)
958
+ let process = Process (
959
+ arguments: arguments,
960
+ environment: environment,
961
+ outputRedirection: . collect,
962
+ loggingHandler: loggingHandler
963
+ )
840
964
process. completionQueue = queue ?? Self . sharedCompletionQueue
841
965
try process. launch ( )
842
966
process. waitUntilExit ( completion)
@@ -851,17 +975,39 @@ extension Process {
851
975
/// - arguments: The arguments for the subprocess.
852
976
/// - environment: The environment to pass to subprocess. By default the current process environment
853
977
/// will be inherited.
978
+ /// - loggingHandler: Handler for logging messages
854
979
/// - Returns: The process result.
855
980
@discardableResult
856
- static public func popen( arguments: [ String ] , environment: [ String : String ] = ProcessEnv . vars) throws -> ProcessResult {
857
- let process = Process ( arguments: arguments, environment: environment, outputRedirection: . collect)
981
+ static public func popen(
982
+ arguments: [ String ] ,
983
+ environment: [ String : String ] = ProcessEnv . vars,
984
+ loggingHandler: LoggingHandler ? = . none
985
+ ) throws -> ProcessResult {
986
+ let process = Process (
987
+ arguments: arguments,
988
+ environment: environment,
989
+ outputRedirection: . collect,
990
+ loggingHandler: loggingHandler
991
+ )
858
992
try process. launch ( )
859
993
return try process. waitUntilExit ( )
860
994
}
861
995
996
+ /// Execute a subprocess and block until it finishes execution
997
+ ///
998
+ /// - Parameters:
999
+ /// - args: The arguments for the subprocess.
1000
+ /// - environment: The environment to pass to subprocess. By default the current process environment
1001
+ /// will be inherited.
1002
+ /// - loggingHandler: Handler for logging messages
1003
+ /// - Returns: The process result.
862
1004
@discardableResult
863
- static public func popen( args: String ... , environment: [ String : String ] = ProcessEnv . vars) throws -> ProcessResult {
864
- return try Process . popen ( arguments: args, environment: environment)
1005
+ static public func popen(
1006
+ args: String ... ,
1007
+ environment: [ String : String ] = ProcessEnv . vars,
1008
+ loggingHandler: LoggingHandler ? = . none
1009
+ ) throws -> ProcessResult {
1010
+ return try Process . popen ( arguments: args, environment: environment, loggingHandler: loggingHandler)
865
1011
}
866
1012
867
1013
/// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
@@ -870,10 +1016,20 @@ extension Process {
870
1016
/// - arguments: The arguments for the subprocess.
871
1017
/// - environment: The environment to pass to subprocess. By default the current process environment
872
1018
/// will be inherited.
1019
+ /// - loggingHandler: Handler for logging messages
873
1020
/// - Returns: The process output (stdout + stderr).
874
1021
@discardableResult
875
- static public func checkNonZeroExit( arguments: [ String ] , environment: [ String : String ] = ProcessEnv . vars) throws -> String {
876
- let process = Process ( arguments: arguments, environment: environment, outputRedirection: . collect)
1022
+ static public func checkNonZeroExit(
1023
+ arguments: [ String ] ,
1024
+ environment: [ String : String ] = ProcessEnv . vars,
1025
+ loggingHandler: LoggingHandler ? = . none
1026
+ ) throws -> String {
1027
+ let process = Process (
1028
+ arguments: arguments,
1029
+ environment: environment,
1030
+ outputRedirection: . collect,
1031
+ loggingHandler: loggingHandler
1032
+ )
877
1033
try process. launch ( )
878
1034
let result = try process. waitUntilExit ( )
879
1035
// Throw if there was a non zero termination.
@@ -883,13 +1039,21 @@ extension Process {
883
1039
return try result. utf8Output ( )
884
1040
}
885
1041
1042
+ /// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
1043
+ ///
1044
+ /// - Parameters:
1045
+ /// - arguments: The arguments for the subprocess.
1046
+ /// - environment: The environment to pass to subprocess. By default the current process environment
1047
+ /// will be inherited.
1048
+ /// - loggingHandler: Handler for logging messages
1049
+ /// - Returns: The process output (stdout + stderr).
886
1050
@discardableResult
887
- static public func checkNonZeroExit( args : String ... , environment : [ String : String ] = ProcessEnv . vars ) throws -> String {
888
- return try checkNonZeroExit ( arguments : args, environment : environment )
889
- }
890
-
891
- public convenience init ( args : String ... , environment : [ String : String ] = ProcessEnv . vars , outputRedirection : OutputRedirection = . collect ) {
892
- self . init ( arguments: args, environment: environment, outputRedirection : outputRedirection )
1051
+ static public func checkNonZeroExit(
1052
+ args: String ... ,
1053
+ environment : [ String : String ] = ProcessEnv . vars ,
1054
+ loggingHandler : LoggingHandler ? = . none
1055
+ ) throws -> String {
1056
+ return try checkNonZeroExit ( arguments: args, environment: environment, loggingHandler : loggingHandler )
893
1057
}
894
1058
}
895
1059
@@ -1032,3 +1196,12 @@ extension FileHandle: WritableByteStream {
1032
1196
}
1033
1197
}
1034
1198
#endif
1199
+
1200
+
1201
+ extension Process {
1202
+ @available ( * , deprecated)
1203
+ fileprivate static func logToStdout( _ message: String ) {
1204
+ stdoutStream <<< message <<< " \n "
1205
+ stdoutStream. flush ( )
1206
+ }
1207
+ }
0 commit comments