@@ -204,11 +204,64 @@ public final class Process {
204204 /// Typealias for stdout/stderr output closure.
205205 public typealias OutputClosure = ( [ UInt8 ] ) -> Void
206206
207- /// Global default setting for verbose.
208- public static var verbose = false
207+ /// Typealias for logging handling closure
208+ public typealias LoggingHandler = ( String ) -> Void
209209
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+ }
212265
213266 /// The current environment.
214267 @available ( * , deprecated, message: " use ProcessEnv.vars instead " )
@@ -238,6 +291,7 @@ public final class Process {
238291 // process execution mutable state
239292 private var state : State = . idle
240293 private let stateLock = Lock ( )
294+
241295 private static let sharedCompletionQueue = DispatchQueue ( label: " org.swift.tools-support-core.process-completion " )
242296 private var completionQueue = Process . sharedCompletionQueue
243297
@@ -286,24 +340,50 @@ public final class Process {
286340 /// will be inherited.
287341 /// - workingDirectory: The path to the directory under which to run the process.
288342 /// - 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.
290343 /// - startNewProcessGroup: If true, a new progress group is created for the child making it
291344 /// continue running even if the parent is killed or interrupted. Default value is true.
345+ /// - loggingHandler: Handler for logging messages
346+ ///
292347 @available ( macOS 10 . 15 , * )
293348 public init (
294349 arguments: [ String ] ,
295350 environment: [ String : String ] = ProcessEnv . vars,
296351 workingDirectory: AbsolutePath ,
297352 outputRedirection: OutputRedirection = . collect,
298- verbose : Bool = Process . verbose ,
299- startNewProcessGroup : Bool = true
353+ startNewProcessGroup : Bool = true ,
354+ loggingHandler : LoggingHandler ? = . none
300355 ) {
301356 self . arguments = arguments
302357 self . environment = environment
303358 self . workingDirectory = workingDirectory
304359 self . outputRedirection = outputRedirection
305- self . verbose = verbose
306360 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+ )
307387 }
308388
309389 /// Create a new process instance.
@@ -316,19 +396,52 @@ public final class Process {
316396 /// - verbose: If true, launch() will print the arguments of the subprocess before launching it.
317397 /// - startNewProcessGroup: If true, a new progress group is created for the child making it
318398 /// continue running even if the parent is killed or interrupted. Default value is true.
399+ /// - loggingHandler: Handler for logging messages
319400 public init (
320401 arguments: [ String ] ,
321402 environment: [ String : String ] = ProcessEnv . vars,
322403 outputRedirection: OutputRedirection = . collect,
323- verbose : Bool = Process . verbose ,
324- startNewProcessGroup : Bool = true
404+ startNewProcessGroup : Bool = true ,
405+ loggingHandler : LoggingHandler ? = . none
325406 ) {
326407 self . arguments = arguments
327408 self . environment = environment
328409 self . workingDirectory = nil
329410 self . outputRedirection = outputRedirection
330- self . verbose = verbose
331411 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+ )
332445 }
333446
334447 /// Returns the path of the the given program if found in the search paths.
@@ -393,9 +506,8 @@ public final class Process {
393506 }
394507
395508 // 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: " " ) )
399511 }
400512
401513 // Look for executable.
@@ -832,11 +944,23 @@ extension Process {
832944 /// - arguments: The arguments for the subprocess.
833945 /// - environment: The environment to pass to subprocess. By default the current process environment
834946 /// 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+ ) {
838957 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+ )
840964 process. completionQueue = queue ?? Self . sharedCompletionQueue
841965 try process. launch ( )
842966 process. waitUntilExit ( completion)
@@ -851,17 +975,39 @@ extension Process {
851975 /// - arguments: The arguments for the subprocess.
852976 /// - environment: The environment to pass to subprocess. By default the current process environment
853977 /// will be inherited.
978+ /// - loggingHandler: Handler for logging messages
854979 /// - Returns: The process result.
855980 @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+ )
858992 try process. launch ( )
859993 return try process. waitUntilExit ( )
860994 }
861995
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.
8621004 @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)
8651011 }
8661012
8671013 /// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
@@ -870,10 +1016,20 @@ extension Process {
8701016 /// - arguments: The arguments for the subprocess.
8711017 /// - environment: The environment to pass to subprocess. By default the current process environment
8721018 /// will be inherited.
1019+ /// - loggingHandler: Handler for logging messages
8731020 /// - Returns: The process output (stdout + stderr).
8741021 @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+ )
8771033 try process. launch ( )
8781034 let result = try process. waitUntilExit ( )
8791035 // Throw if there was a non zero termination.
@@ -883,13 +1039,21 @@ extension Process {
8831039 return try result. utf8Output ( )
8841040 }
8851041
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).
8861050 @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 )
8931057 }
8941058}
8951059
@@ -1032,3 +1196,12 @@ extension FileHandle: WritableByteStream {
10321196 }
10331197}
10341198#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