11/*
22 This source file is part of the Swift.org open source project
3-
4- Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
3+
4+ Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
55 Licensed under Apache License v2.0 with Runtime Library Exception
6-
6+
77 See http://swift.org/LICENSE.txt for license information
88 See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99*/
@@ -92,7 +92,7 @@ public struct ProcessResult: CustomStringConvertible {
9292 self . stderrOutput = stderrOutput
9393 self . exitStatus = exitStatus
9494 }
95-
95+
9696 /// Converts stdout output bytes to string, assuming they're UTF8.
9797 public func utf8Output( ) throws -> String {
9898 return String ( decoding: try output. get ( ) , as: Unicode . UTF8. self)
@@ -122,15 +122,15 @@ public final class Process: ObjectIdentifierProtocol {
122122 /// The program requested to be executed cannot be found on the existing search paths, or is not executable.
123123 case missingExecutableProgram( program: String )
124124 }
125-
125+
126126 public enum OutputRedirection {
127127 /// Do not redirect the output
128128 case none
129129 /// Collect stdout and stderr output and provide it back via ProcessResult object
130130 case collect
131131 /// Stream stdout and stderr via the corresponding closures
132132 case stream( stdout: OutputClosure , stderr: OutputClosure )
133-
133+
134134 public var redirectsOutput : Bool {
135135 switch self {
136136 case . none:
@@ -139,7 +139,7 @@ public final class Process: ObjectIdentifierProtocol {
139139 return true
140140 }
141141 }
142-
142+
143143 public var outputClosures : ( stdoutClosure: OutputClosure , stderrClosure: OutputClosure ) ? {
144144 switch self {
145145 case . stream( let stdoutClosure, let stderrClosure) :
@@ -176,6 +176,9 @@ public final class Process: ObjectIdentifierProtocol {
176176 /// The environment with which the process was executed.
177177 public let environment : [ String : String ]
178178
179+ /// The path to the directory under which to run the process.
180+ public let workingDirectory : AbsolutePath ?
181+
179182 /// The process id of the spawned process, available after the process is launched.
180183 #if os(Windows)
181184 private var _process : Foundation . Process ?
@@ -213,7 +216,7 @@ public final class Process: ObjectIdentifierProtocol {
213216 /// Queue to protect reading/writing on map of validated executables.
214217 private static let executablesQueue = DispatchQueue (
215218 label: " org.swift.swiftpm.process.findExecutable " )
216-
219+
217220 /// Indicates if a new progress group is created for the child process.
218221 private let startNewProcessGroup : Bool
219222
@@ -223,6 +226,36 @@ public final class Process: ObjectIdentifierProtocol {
223226 /// Value: Path to the executable, if found.
224227 static private var validatedExecutablesMap = [ String: AbsolutePath? ] ( )
225228
229+ #if os(macOS)
230+ /// Create a new process instance.
231+ ///
232+ /// - Parameters:
233+ /// - arguments: The arguments for the subprocess.
234+ /// - environment: The environment to pass to subprocess. By default the current process environment
235+ /// will be inherited.
236+ /// - workingDirectory: The path to the directory under which to run the process.
237+ /// - outputRedirection: How process redirects its output. Default value is .collect.
238+ /// - verbose: If true, launch() will print the arguments of the subprocess before launching it.
239+ /// - startNewProcessGroup: If true, a new progress group is created for the child making it
240+ /// continue running even if the parent is killed or interrupted. Default value is true.
241+ @available ( macOS 10 . 15 , * )
242+ public init (
243+ arguments: [ String ] ,
244+ environment: [ String : String ] = ProcessEnv . vars,
245+ workingDirectory: AbsolutePath ,
246+ outputRedirection: OutputRedirection = . collect,
247+ verbose: Bool = Process . verbose,
248+ startNewProcessGroup: Bool = true
249+ ) {
250+ self . arguments = arguments
251+ self . environment = environment
252+ self . workingDirectory = workingDirectory
253+ self . outputRedirection = outputRedirection
254+ self . verbose = verbose
255+ self . startNewProcessGroup = startNewProcessGroup
256+ }
257+ #endif
258+
226259 /// Create a new process instance.
227260 ///
228261 /// - Parameters:
@@ -242,6 +275,7 @@ public final class Process: ObjectIdentifierProtocol {
242275 ) {
243276 self . arguments = arguments
244277 self . environment = environment
278+ self . workingDirectory = nil
245279 self . outputRedirection = outputRedirection
246280 self . verbose = verbose
247281 self . startNewProcessGroup = startNewProcessGroup
@@ -370,6 +404,17 @@ public final class Process: ObjectIdentifierProtocol {
370404 posix_spawn_file_actions_init ( & fileActions)
371405 defer { posix_spawn_file_actions_destroy ( & fileActions) }
372406
407+ #if os(macOS)
408+ if let workingDirectory = workingDirectory? . pathString {
409+ // The only way to set a workingDirectory is using an availability-gated initializer, so we don't need
410+ // to handle the case where the posix_spawn_file_actions_addchdir_np method is unavailable. This check only
411+ // exists here to make the compiler happy.
412+ if #available( macOS 10 . 15 , * ) {
413+ posix_spawn_file_actions_addchdir_np ( & fileActions, workingDirectory)
414+ }
415+ }
416+ #endif
417+
373418 // Workaround for https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=89e435f3559c53084498e9baad22172b64429362
374419 // Change allowing for newer version of glibc
375420 guard let devNull = strdup ( " /dev/null " ) else {
@@ -408,7 +453,7 @@ public final class Process: ObjectIdentifierProtocol {
408453
409454 if outputRedirection. redirectsOutput {
410455 let outputClosures = outputRedirection. outputClosures
411-
456+
412457 // Close the write end of the output pipe.
413458 try close ( fd: & outputPipe[ 1 ] )
414459
@@ -682,7 +727,7 @@ extension ProcessResult.Error: CustomStringConvertible {
682727 stream <<< " \n "
683728 }
684729 }
685-
730+
686731 return stream. bytes. description
687732 }
688733 }
0 commit comments