@@ -25,14 +25,20 @@ private let environmentVariables =
2525 } )
2626 : ProcessInfo . processInfo. environment
2727
28- /// Runs `executable` with `arguments`, returning stdout trimmed of whitespace.
29- private func runProcessForOutput(
30- _ executable: String , arguments: [ String ]
28+ /// Returns the standard output from running `commandLine` passing
29+ /// `arguments`, trimmed of whitespace.
30+ ///
31+ /// If `commandLine` contains no path separators, it is looked up in `PATH`.
32+ ///
33+ /// - Parameter setSKDRoot: true iff xcrun should be used to set the SDKROOT
34+ /// environment variable for the process.
35+ private func standardOutputOf(
36+ _ commandLine: [ String ] , setSDKRoot: Bool = true
3137) throws -> String {
32- let result = try runProcess ( executable , arguments : arguments )
38+ let result = try resultsOfRunning ( commandLine , setSDKRoot : setSDKRoot )
3339
3440 guard result. exitStatus == 0 else {
35- throw Failure ( " \( executable ) \( arguments ) exited with \( result. exitStatus) " )
41+ throw Failure ( " \( commandLine ) exited with \( result. exitStatus) " )
3642 }
3743
3844 return result. stdout. trimmingCharacters ( in: . whitespacesAndNewlines)
@@ -70,7 +76,7 @@ private func findWindowsExecutableInPath(_ command: String) throws -> String {
7076 throw Failure ( " \( whereExe) doesn't exist " )
7177 }
7278
73- let output = try runProcessForOutput ( whereExe, arguments : [ command] )
79+ let output = try standardOutputOf ( [ whereExe, command] )
7480
7581 // where.exe returns the first match on the first line
7682 guard let r = output. split ( separator: " \n " , maxSplits: 1 ) . first else {
@@ -79,26 +85,57 @@ private func findWindowsExecutableInPath(_ command: String) throws -> String {
7985 return String ( r)
8086}
8187
88+ /// The value to set for SDKROOT in subprocess environments on macOS.
89+ internal let swiftSDKRoot : String =
90+ if isMacOS {
91+ ProcessInfo . processInfo. environment [ " SDKROOT " ]
92+ ?? { ( ) -> String in
93+ do {
94+ // Don't set SDKROOT in the environment to avoid infinite recursion.
95+ return try standardOutputOf (
96+ [ " /usr/bin/xcrun " , " --show-sdk-path " ] , setSDKRoot: false )
97+ } catch let e {
98+ fatalError ( " \( e) " )
99+ }
100+ } ( )
101+ } else {
102+ " UNUSED "
103+ }
104+
82105/// The SDK root path on macOS.
83106///
84107/// - Precondition: running on macOS
85108private func sdkRootPath( ) throws -> String {
86109 precondition ( isMacOS)
87- let path = try runProcessForOutput ( " /usr/bin/xcrun " , arguments : [ " --show-sdk-path " ] )
110+ let path = try standardOutputOf ( [ " /usr/bin/xcrun " , " --show-sdk-path " ] )
88111 if path. isEmpty { throw Failure ( " 'xcrun --show-sdk-path' returned empty string " ) }
89112 return path
90113}
91114
92- /// Returns a `Process` that runs `command` with the given `arguments`.
115+ /// Output from running a process.
116+ struct ProcessResults {
117+ /// Standard output as UTF-8 string.
118+ let stdout : String
119+ /// Standard error as UTF-8 string.
120+ let stderr : String
121+ /// Process exit status.
122+ let exitStatus : Int32
123+ }
124+
125+ /// Returns the result of running `command` passing `arguments`.
126+ ///
127+ /// If `commandLine[0]` contains no path separators, the executable is
128+ /// looked up in `PATH`.
93129///
94- /// If `command` contains no path separators, it will be found in
95- /// `PATH`. On macOS, ensures SDKROOT is set in the environment in
96- /// case the command needs it.
97- func processForCommand( _ command: String , arguments: [ String ] ) throws -> Process {
130+ /// - Parameter setSKDRoot: true iff xcrun should be used to set the SDKROOT
131+ /// environment variable for the process.
132+ func resultsOfRunning( _ commandLine: [ String ] , setSDKRoot: Bool = true ) throws -> ProcessResults {
98133 let p = Process ( )
99134
100- p. arguments = arguments
101- // If command contains path separators, use it directly without PATH search
135+ let command = commandLine. first!
136+ var arguments = Array ( commandLine. dropFirst ( ) )
137+
138+ // If executable contains path separators, use it directly without PATH search
102139 if command. contains ( isWindows ? " \\ " : " / " ) {
103140 p. executableURL = URL ( fileURLWithPath: command)
104141 } else {
@@ -107,44 +144,24 @@ func processForCommand(_ command: String, arguments: [String]) throws -> Process
107144 } else {
108145 // Let env find and run the executable.
109146 p. executableURL = URL ( fileURLWithPath: " /usr/bin/env " )
110- p . arguments = [ command] + arguments
147+ arguments. insert ( command, at : 0 )
111148 }
112149 }
150+ p. arguments = Array ( arguments)
113151
114- // Set SDKROOT on macOS if needed, but skip for xcrun itself to prevent infinite recursion
115- let isXcrun = command. hasSuffix ( " /xcrun " ) || command == " xcrun "
116- if isMacOS && !isXcrun {
117- var environment = ProcessInfo . processInfo. environment
118- if environment [ " SDKROOT " ] == nil {
119- environment [ " SDKROOT " ] = try sdkRootPath ( )
120- p. environment = environment
121- }
152+ if setSDKRoot && isMacOS {
153+ var e = ProcessInfo . processInfo. environment
154+ e [ " SDKROOT " ] = swiftSDKRoot
155+ p. environment = e
122156 }
123157
124- return p
125- }
126-
127- /// Output from running a process.
128- struct ProcessOutput {
129- /// Standard output as UTF-8 string.
130- let stdout : String
131- /// Standard error as UTF-8 string.
132- let stderr : String
133- /// Process exit status.
134- let exitStatus : Int32
135- }
136-
137- /// Runs `command` with `arguments`, returning captured output and exit status.
138- func runProcess( _ command: String , arguments: [ String ] ) throws -> ProcessOutput {
139- let process = try processForCommand ( command, arguments: arguments)
140-
141158 let stdoutPipe = Pipe ( )
142159 let stderrPipe = Pipe ( )
143- process . standardOutput = stdoutPipe
144- process . standardError = stderrPipe
160+ p . standardOutput = stdoutPipe
161+ p . standardError = stderrPipe
145162
146163 do {
147- try process . run ( )
164+ try p . run ( )
148165 } catch {
149166 let commandLine =
150167 arguments. isEmpty
@@ -158,16 +175,16 @@ func runProcess(_ command: String, arguments: [String]) throws -> ProcessOutput
158175 let stdoutData = readPipeInBackground ( stdoutPipe)
159176 let stderrData = readPipeInBackground ( stderrPipe)
160177
161- process . waitUntilExit ( )
178+ p . waitUntilExit ( )
162179
163180 // Retrieve the data (blocks until background reads complete)
164181 let stdout = try decodeUTF8 ( stdoutData ( ) , as: " \( command) stdout " )
165182 let stderr = try decodeUTF8 ( stderrData ( ) , as: " \( command) stderr " )
166183
167- return ProcessOutput (
184+ return ProcessResults (
168185 stdout: stdout,
169186 stderr: stderr,
170- exitStatus: process . terminationStatus
187+ exitStatus: p . terminationStatus
171188 )
172189}
173190
0 commit comments