@@ -21,18 +21,20 @@ import Foundation
2121import TSCLibc
2222import Dispatch
2323
24+ import _Concurrency
25+
2426/// Process result data which is available after process termination.
25- public struct ProcessResult : CustomStringConvertible {
27+ public struct ProcessResult : CustomStringConvertible , Sendable {
2628
27- public enum Error : Swift . Error {
29+ public enum Error : Swift . Error , Sendable {
2830 /// The output is not a valid UTF8 sequence.
2931 case illegalUTF8Sequence
3032
3133 /// The process had a non zero exit.
3234 case nonZeroExit( ProcessResult )
3335 }
3436
35- public enum ExitStatus : Equatable {
37+ public enum ExitStatus : Equatable , Sendable {
3638 /// The process was terminated normally with a exit code.
3739 case terminated( code: Int32 )
3840#if os(Windows)
@@ -125,12 +127,18 @@ public struct ProcessResult: CustomStringConvertible {
125127 }
126128}
127129
130+ #if swift(<5.6)
131+ extension Process : UnsafeSendable { }
132+ #else
133+ extension Process : @unchecked Sendable { }
134+ #endif
135+
128136/// Process allows spawning new subprocesses and working with them.
129137///
130138/// Note: This class is thread safe.
131139public final class Process {
132140 /// Errors when attempting to invoke a process
133- public enum Error : Swift . Error {
141+ public enum Error : Swift . Error , Sendable {
134142 /// The program requested to be executed cannot be found on the existing search paths, or is not executable.
135143 case missingExecutableProgram( program: String )
136144
@@ -807,7 +815,29 @@ public final class Process {
807815 #endif // POSIX implementation
808816 }
809817
818+ /// Executes the process I/O state machine, returning the result when finished.
819+ @available ( macOS 10 . 15 , iOS 13 . 0 , tvOS 13 . 0 , watchOS 6 . 0 , * )
820+ @discardableResult
821+ public func waitUntilExit( ) async throws -> ProcessResult {
822+ #if compiler(>=5.6)
823+ return try await withCheckedThrowingContinuation { continuation in
824+ waitUntilExit ( continuation. resume ( with: ) )
825+ }
826+ #else
827+ if #available( macOS 12 . 0 , iOS 15 . 0 , tvOS 15 . 0 , watchOS 8 . 0 , * ) {
828+ return try await withCheckedThrowingContinuation { continuation in
829+ waitUntilExit ( continuation. resume ( with: ) )
830+ }
831+ } else {
832+ preconditionFailure ( " Unsupported with Swift 5.5 on this OS version " )
833+ }
834+ #endif
835+ }
836+
810837 /// Blocks the calling process until the subprocess finishes execution.
838+ #if compiler(>=5.7)
839+ @available ( * , noasync)
840+ #endif
811841 @discardableResult
812842 public func waitUntilExit( ) throws -> ProcessResult {
813843 let group = DispatchGroup ( )
@@ -938,6 +968,88 @@ public final class Process {
938968 }
939969}
940970
971+ extension Process {
972+ /// Execute a subprocess and returns the result when it finishes execution
973+ ///
974+ /// - Parameters:
975+ /// - arguments: The arguments for the subprocess.
976+ /// - environment: The environment to pass to subprocess. By default the current process environment
977+ /// will be inherited.
978+ /// - loggingHandler: Handler for logging messages
979+ @available ( macOS 10 . 15 , * )
980+ static public func popen(
981+ arguments: [ String ] ,
982+ environment: [ String : String ] = ProcessEnv . vars,
983+ loggingHandler: LoggingHandler ? = . none
984+ ) async throws -> ProcessResult {
985+ let process = Process (
986+ arguments: arguments,
987+ environment: environment,
988+ outputRedirection: . collect,
989+ loggingHandler: loggingHandler
990+ )
991+ try process. launch ( )
992+ return try await process. waitUntilExit ( )
993+ }
994+
995+ /// Execute a subprocess and returns the result when it finishes execution
996+ ///
997+ /// - Parameters:
998+ /// - args: The arguments for the subprocess.
999+ /// - environment: The environment to pass to subprocess. By default the current process environment
1000+ /// will be inherited.
1001+ /// - loggingHandler: Handler for logging messages
1002+ @available ( macOS 10 . 15 , * )
1003+ static public func popen(
1004+ args: String ... ,
1005+ environment: [ String : String ] = ProcessEnv . vars,
1006+ loggingHandler: LoggingHandler ? = . none
1007+ ) async throws -> ProcessResult {
1008+ try await popen ( arguments: args, environment: environment, loggingHandler: loggingHandler)
1009+ }
1010+
1011+ /// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
1012+ ///
1013+ /// - Parameters:
1014+ /// - arguments: The arguments for the subprocess.
1015+ /// - environment: The environment to pass to subprocess. By default the current process environment
1016+ /// will be inherited.
1017+ /// - loggingHandler: Handler for logging messages
1018+ /// - Returns: The process output (stdout + stderr).
1019+ @available ( macOS 10 . 15 , * )
1020+ @discardableResult
1021+ static public func checkNonZeroExit(
1022+ arguments: [ String ] ,
1023+ environment: [ String : String ] = ProcessEnv . vars,
1024+ loggingHandler: LoggingHandler ? = . none
1025+ ) async throws -> String {
1026+ let result = try await popen ( arguments: arguments, environment: environment, loggingHandler: loggingHandler)
1027+ // Throw if there was a non zero termination.
1028+ guard result. exitStatus == . terminated( code: 0 ) else {
1029+ throw ProcessResult . Error. nonZeroExit ( result)
1030+ }
1031+ return try result. utf8Output ( )
1032+ }
1033+
1034+ /// Execute a subprocess and get its (UTF-8) output if it has a non zero exit.
1035+ ///
1036+ /// - Parameters:
1037+ /// - args: The arguments for the subprocess.
1038+ /// - environment: The environment to pass to subprocess. By default the current process environment
1039+ /// will be inherited.
1040+ /// - loggingHandler: Handler for logging messages
1041+ /// - Returns: The process output (stdout + stderr).
1042+ @available ( macOS 10 . 15 , * )
1043+ @discardableResult
1044+ static public func checkNonZeroExit(
1045+ args: String ... ,
1046+ environment: [ String : String ] = ProcessEnv . vars,
1047+ loggingHandler: LoggingHandler ? = . none
1048+ ) async throws -> String {
1049+ try await checkNonZeroExit ( arguments: args, environment: environment, loggingHandler: loggingHandler)
1050+ }
1051+ }
1052+
9411053extension Process {
9421054 /// Execute a subprocess and calls completion block when it finishes execution
9431055 ///
@@ -948,6 +1060,9 @@ extension Process {
9481060 /// - loggingHandler: Handler for logging messages
9491061 /// - queue: Queue to use for callbacks
9501062 /// - completion: A completion handler to return the process result
1063+ #if compiler(>=5.7)
1064+ @available ( * , noasync)
1065+ #endif
9511066 static public func popen(
9521067 arguments: [ String ] ,
9531068 environment: [ String : String ] = ProcessEnv . vars,
@@ -982,6 +1097,9 @@ extension Process {
9821097 /// will be inherited.
9831098 /// - loggingHandler: Handler for logging messages
9841099 /// - Returns: The process result.
1100+ #if compiler(>=5.7)
1101+ @available ( * , noasync)
1102+ #endif
9851103 @discardableResult
9861104 static public func popen(
9871105 arguments: [ String ] ,
@@ -1006,6 +1124,9 @@ extension Process {
10061124 /// will be inherited.
10071125 /// - loggingHandler: Handler for logging messages
10081126 /// - Returns: The process result.
1127+ #if compiler(>=5.7)
1128+ @available ( * , noasync)
1129+ #endif
10091130 @discardableResult
10101131 static public func popen(
10111132 args: String ... ,
@@ -1023,6 +1144,9 @@ extension Process {
10231144 /// will be inherited.
10241145 /// - loggingHandler: Handler for logging messages
10251146 /// - Returns: The process output (stdout + stderr).
1147+ #if compiler(>=5.7)
1148+ @available ( * , noasync)
1149+ #endif
10261150 @discardableResult
10271151 static public func checkNonZeroExit(
10281152 arguments: [ String ] ,
@@ -1052,6 +1176,9 @@ extension Process {
10521176 /// will be inherited.
10531177 /// - loggingHandler: Handler for logging messages
10541178 /// - Returns: The process output (stdout + stderr).
1179+ #if compiler(>=5.7)
1180+ @available ( * , noasync)
1181+ #endif
10551182 @discardableResult
10561183 static public func checkNonZeroExit(
10571184 args: String ... ,
0 commit comments