@@ -21,18 +21,20 @@ import Foundation
21
21
import TSCLibc
22
22
import Dispatch
23
23
24
+ import _Concurrency
25
+
24
26
/// Process result data which is available after process termination.
25
- public struct ProcessResult : CustomStringConvertible {
27
+ public struct ProcessResult : CustomStringConvertible , Sendable {
26
28
27
- public enum Error : Swift . Error {
29
+ public enum Error : Swift . Error , Sendable {
28
30
/// The output is not a valid UTF8 sequence.
29
31
case illegalUTF8Sequence
30
32
31
33
/// The process had a non zero exit.
32
34
case nonZeroExit( ProcessResult )
33
35
}
34
36
35
- public enum ExitStatus : Equatable {
37
+ public enum ExitStatus : Equatable , Sendable {
36
38
/// The process was terminated normally with a exit code.
37
39
case terminated( code: Int32 )
38
40
#if os(Windows)
@@ -125,12 +127,18 @@ public struct ProcessResult: CustomStringConvertible {
125
127
}
126
128
}
127
129
130
+ #if swift(<5.6)
131
+ extension Process : UnsafeSendable { }
132
+ #else
133
+ extension Process : @unchecked Sendable { }
134
+ #endif
135
+
128
136
/// Process allows spawning new subprocesses and working with them.
129
137
///
130
138
/// Note: This class is thread safe.
131
139
public final class Process {
132
140
/// Errors when attempting to invoke a process
133
- public enum Error : Swift . Error {
141
+ public enum Error : Swift . Error , Sendable {
134
142
/// The program requested to be executed cannot be found on the existing search paths, or is not executable.
135
143
case missingExecutableProgram( program: String )
136
144
@@ -807,7 +815,29 @@ public final class Process {
807
815
#endif // POSIX implementation
808
816
}
809
817
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
+
810
837
/// Blocks the calling process until the subprocess finishes execution.
838
+ #if compiler(>=5.8)
839
+ @available ( * , noasync)
840
+ #endif
811
841
@discardableResult
812
842
public func waitUntilExit( ) throws -> ProcessResult {
813
843
let group = DispatchGroup ( )
@@ -938,6 +968,88 @@ public final class Process {
938
968
}
939
969
}
940
970
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
+
941
1053
extension Process {
942
1054
/// Execute a subprocess and calls completion block when it finishes execution
943
1055
///
@@ -948,6 +1060,9 @@ extension Process {
948
1060
/// - loggingHandler: Handler for logging messages
949
1061
/// - queue: Queue to use for callbacks
950
1062
/// - completion: A completion handler to return the process result
1063
+ #if compiler(>=5.8)
1064
+ @available ( * , noasync)
1065
+ #endif
951
1066
static public func popen(
952
1067
arguments: [ String ] ,
953
1068
environment: [ String : String ] = ProcessEnv . vars,
@@ -982,6 +1097,9 @@ extension Process {
982
1097
/// will be inherited.
983
1098
/// - loggingHandler: Handler for logging messages
984
1099
/// - Returns: The process result.
1100
+ #if compiler(>=5.8)
1101
+ @available ( * , noasync)
1102
+ #endif
985
1103
@discardableResult
986
1104
static public func popen(
987
1105
arguments: [ String ] ,
@@ -1006,6 +1124,9 @@ extension Process {
1006
1124
/// will be inherited.
1007
1125
/// - loggingHandler: Handler for logging messages
1008
1126
/// - Returns: The process result.
1127
+ #if compiler(>=5.8)
1128
+ @available ( * , noasync)
1129
+ #endif
1009
1130
@discardableResult
1010
1131
static public func popen(
1011
1132
args: String ... ,
@@ -1023,6 +1144,9 @@ extension Process {
1023
1144
/// will be inherited.
1024
1145
/// - loggingHandler: Handler for logging messages
1025
1146
/// - Returns: The process output (stdout + stderr).
1147
+ #if compiler(>=5.8)
1148
+ @available ( * , noasync)
1149
+ #endif
1026
1150
@discardableResult
1027
1151
static public func checkNonZeroExit(
1028
1152
arguments: [ String ] ,
@@ -1052,6 +1176,9 @@ extension Process {
1052
1176
/// will be inherited.
1053
1177
/// - loggingHandler: Handler for logging messages
1054
1178
/// - Returns: The process output (stdout + stderr).
1179
+ #if compiler(>=5.8)
1180
+ @available ( * , noasync)
1181
+ #endif
1055
1182
@discardableResult
1056
1183
static public func checkNonZeroExit(
1057
1184
args: String ... ,
0 commit comments