Skip to content

Commit ab47048

Browse files
committed
Clean up process handling.
1 parent 547bc08 commit ab47048

File tree

3 files changed

+69
-53
lines changed

3 files changed

+69
-53
lines changed

Sources/gyb-swift/ExecutionContext.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ struct CodeGenerator {
141141
throw Failure("Failed to write temporary Swift file to '\(temp.path)'", error)
142142
}
143143

144-
let result = try runProcess("swift", arguments: [temp.platformString])
144+
let result = try resultsOfRunning(["swift", temp.platformString])
145145

146146
guard result.exitStatus == 0 else {
147147
throw GYBError.executionFailed(filename: filename, errorOutput: result.stderr)
@@ -202,9 +202,8 @@ struct CodeGenerator {
202202

203203
/// Compiles Swift source file to executable.
204204
private func compileSwiftCode(source: URL, output: URL, moduleCache: URL) throws {
205-
let result = try runProcess(
206-
"swiftc",
207-
arguments: [
205+
let result = try resultsOfRunning(
206+
["swiftc",
208207
source.platformString,
209208
"-o", output.platformString,
210209
"-module-cache-path", moduleCache.platformString,
@@ -217,7 +216,7 @@ struct CodeGenerator {
217216

218217
/// Runs compiled executable and returns its output.
219218
private func runCompiledExecutable(_ executable: URL) throws -> String {
220-
let result = try runProcess(executable.platformString, arguments: [])
219+
let result = try resultsOfRunning([executable.platformString])
221220

222221
guard result.exitStatus == 0 else {
223222
throw GYBError.executionFailed(filename: filename, errorOutput: result.stderr)

Sources/gyb-swift/ProcessUtilities.swift

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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
85108
private 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

Tests/gyb-swiftTests/SourceLocationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private func runSwiftScript(
128128
// On Windows, atomically: true can cause file locking issues
129129
try swiftCode.write(toFile: tempFile, atomically: !isWindows, encoding: .utf8)
130130

131-
let result = try runProcess("swift", arguments: [tempFile])
131+
let result = try resultsOfRunning(["swift", tempFile])
132132

133133
if result.exitStatus != 0 {
134134
var diagnostics = "Exit code: \(result.exitStatus)"
@@ -222,7 +222,7 @@ func hasSyntaxErrors(_ code: String) -> Bool {
222222
@Test("Swift executable is accessible")
223223
func swiftExecutableAccessible() throws {
224224
do {
225-
let result = try runProcess("swift", arguments: ["--version"])
225+
let result = try resultsOfRunning(["swift", "--version"])
226226

227227
print("Swift version check - Exit code: \(result.exitStatus)")
228228
if !result.stdout.isEmpty {

0 commit comments

Comments
 (0)