Skip to content

Commit 4c6f04c

Browse files
Boukeparkera
authored andcommitted
[NSTask] Change argv[0] to full path + fixes combining stdout+stderr (#353)
* Clarified TestNSTask tests * [NSTask] Redirecting both stdout and stderr fails * [NSTask] Fix issue capturing stdout and stderr to same pipe * [NSTask] Pass complete path as argv[0] This allows executables to derive their own absolute path. SwiftPM relies on this value to derive the compiler path and fake toolchain path.
1 parent ad02ee6 commit 4c6f04c

File tree

2 files changed

+100
-48
lines changed

2 files changed

+100
-48
lines changed

Foundation/NSTask.swift

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public class NSTask : NSObject {
194194

195195
// Convert the arguments array into a posix_spawn-friendly format
196196

197-
var args = [launchPath.lastPathComponent]
197+
var args = [launchPath]
198198
if let arguments = self.arguments {
199199
args.append(contentsOf: arguments)
200200
}
@@ -307,61 +307,52 @@ public class NSTask : NSObject {
307307
#else
308308
var fileActions: posix_spawn_file_actions_t = posix_spawn_file_actions_t()
309309
#endif
310-
posix_spawn_file_actions_init(&fileActions)
310+
try! posix(posix_spawn_file_actions_init(&fileActions))
311+
defer { posix_spawn_file_actions_destroy(&fileActions) }
311312

312-
switch self.standardInput {
313+
switch standardInput {
313314
case let pipe as NSPipe:
314-
posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForReading.fileDescriptor, STDIN_FILENO)
315-
posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForWriting.fileDescriptor)
315+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForReading.fileDescriptor, STDIN_FILENO))
316+
try! posix(posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForWriting.fileDescriptor))
316317
case let handle as NSFileHandle:
317-
posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDIN_FILENO)
318-
posix_spawn_file_actions_addclose(&fileActions, handle.fileDescriptor)
318+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDIN_FILENO))
319319
default: break
320320
}
321321

322-
switch self.standardOutput {
322+
switch standardOutput {
323323
case let pipe as NSPipe:
324-
posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
325-
posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForWriting.fileDescriptor)
324+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO))
325+
try! posix(posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForReading.fileDescriptor))
326326
case let handle as NSFileHandle:
327-
posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDOUT_FILENO)
328-
posix_spawn_file_actions_addclose(&fileActions, handle.fileDescriptor)
327+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDOUT_FILENO))
329328
default: break
330329
}
331330

332-
switch self.standardError {
331+
switch standardError {
333332
case let pipe as NSPipe:
334-
posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
335-
posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForWriting.fileDescriptor)
333+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO))
334+
try! posix(posix_spawn_file_actions_addclose(&fileActions, pipe.fileHandleForReading.fileDescriptor))
336335
case let handle as NSFileHandle:
337-
posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDERR_FILENO)
338-
posix_spawn_file_actions_addclose(&fileActions, handle.fileDescriptor)
336+
try! posix(posix_spawn_file_actions_adddup2(&fileActions, handle.fileDescriptor, STDERR_FILENO))
339337
default: break
340338
}
341339

342340
// Launch
343341

344342
var pid = pid_t()
345-
let status = posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp)
346-
347-
// cleanup file_actions
348-
posix_spawn_file_actions_destroy(&fileActions)
343+
try! posix(posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp))
349344

350345
// Close the write end of the input and output pipes.
351-
if let pipe = self.standardInput as? NSPipe {
346+
if let pipe = standardInput as? NSPipe {
352347
pipe.fileHandleForReading.closeFile()
353348
}
354-
if let pipe = self.standardOutput as? NSPipe {
349+
if let pipe = standardOutput as? NSPipe {
355350
pipe.fileHandleForWriting.closeFile()
356351
}
357-
if let pipe = self.standardError as? NSPipe {
352+
if let pipe = standardError as? NSPipe {
358353
pipe.fileHandleForWriting.closeFile()
359354
}
360355

361-
guard status == 0 else {
362-
fatalError()
363-
}
364-
365356
close(taskSocketPair[1])
366357

367358
self.runLoop = NSRunLoop.currentRunLoop()
@@ -427,4 +418,9 @@ extension NSTask {
427418

428419
public let NSTaskDidTerminateNotification: String = "NSTaskDidTerminateNotification"
429420

430-
421+
private func posix(_ code: Int32) throws {
422+
switch code {
423+
case 0: return
424+
default: throw NSError(domain: NSPOSIXErrorDomain, code: Int(code), userInfo: nil)
425+
}
426+
}

TestFoundation/TestNSTask.swift

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class TestNSTask : XCTestCase {
2727
("test_pipe_stdin", test_pipe_stdin),
2828
("test_pipe_stdout", test_pipe_stdout),
2929
("test_pipe_stderr", test_pipe_stderr),
30+
("test_pipe_stdout_and_stderr_same_pipe", test_pipe_stdout_and_stderr_same_pipe),
3031
("test_file_stdout", test_file_stdout),
3132
("test_passthrough_environment", test_passthrough_environment),
3233
("test_no_environment", test_no_environment),
@@ -163,7 +164,29 @@ class TestNSTask : XCTestCase {
163164
XCTFail("Could not read stdout")
164165
return
165166
}
166-
XCTAssertEqual(string, "cat: invalid_file_name: No such file or directory\n")
167+
XCTAssertEqual(string, "/bin/cat: invalid_file_name: No such file or directory\n")
168+
}
169+
170+
func test_pipe_stdout_and_stderr_same_pipe() {
171+
let task = NSTask()
172+
173+
task.launchPath = "/bin/cat"
174+
task.arguments = ["invalid_file_name"]
175+
176+
let pipe = NSPipe()
177+
task.standardOutput = pipe
178+
task.standardError = pipe
179+
180+
task.launch()
181+
task.waitUntilExit()
182+
XCTAssertEqual(task.terminationStatus, 1)
183+
184+
let data = pipe.fileHandleForReading.availableData
185+
guard let string = String(data: data, encoding: NSASCIIStringEncoding) else {
186+
XCTFail("Could not read stdout")
187+
return
188+
}
189+
XCTAssertEqual(string, "/bin/cat: invalid_file_name: No such file or directory\n")
167190
}
168191

169192
func test_file_stdout() {
@@ -188,19 +211,36 @@ class TestNSTask : XCTestCase {
188211
XCTAssertEqual(string, "/usr/bin/which\n")
189212
}
190213
}
191-
214+
192215
func test_passthrough_environment() {
193-
XCTAssertGreaterThan(env(environment: nil).count, 0)
216+
do {
217+
let output = try runTask(["/usr/bin/env"], environment: nil)
218+
let env = try parseEnv(output)
219+
XCTAssertGreaterThan(env.count, 0)
220+
} catch let error {
221+
XCTFail("Test failed: \(error)")
222+
}
194223
}
195224

196225
func test_no_environment() {
197-
XCTAssertEqual(env(environment: [:]).count, 0)
226+
do {
227+
let output = try runTask(["/usr/bin/env"], environment: [:])
228+
let env = try parseEnv(output)
229+
XCTAssertEqual(env.count, 0)
230+
} catch let error {
231+
XCTFail("Test failed: \(error)")
232+
}
198233
}
199234

200235
func test_custom_environment() {
201-
let input = ["HELLO": "WORLD", "HOME": "CUPERTINO"]
202-
let output = env(environment: input)
203-
XCTAssertEqual(output, input)
236+
do {
237+
let input = ["HELLO": "WORLD", "HOME": "CUPERTINO"]
238+
let output = try runTask(["/usr/bin/env"], environment: input)
239+
let env = try parseEnv(output)
240+
XCTAssertEqual(env, input)
241+
} catch let error {
242+
XCTFail("Test failed: \(error)")
243+
}
204244
}
205245
}
206246

@@ -216,31 +256,47 @@ private func mkstemp(template: String, body: @noescape (NSFileHandle) throws ->
216256
}
217257
}
218258

219-
private func env(environment: [String: String]?) -> [String: String] {
259+
private enum Error: ErrorProtocol {
260+
case TerminationStatus(Int32)
261+
case UnicodeDecodingError(NSData)
262+
case InvalidEnvironmentVariable(String)
263+
}
264+
265+
private func runTask(_ arguments: [String], environment: [String: String]? = nil) throws -> String {
220266
let task = NSTask()
221-
task.launchPath = "/usr/bin/env"
267+
268+
var arguments = arguments
269+
task.launchPath = arguments.removeFirst()
270+
task.arguments = arguments
222271
task.environment = environment
223272

224273
let pipe = NSPipe()
225274
task.standardOutput = pipe
226-
275+
task.standardError = pipe
227276
task.launch()
228277
task.waitUntilExit()
229-
XCTAssertEqual(task.terminationStatus, 0)
278+
279+
guard task.terminationStatus == 0 else {
280+
throw Error.TerminationStatus(task.terminationStatus)
281+
}
230282

231283
let data = pipe.fileHandleForReading.availableData
232-
guard let string = String(data: data, encoding: NSUTF8StringEncoding) else {
233-
XCTFail("Could not read stdout")
234-
return [:]
284+
guard let output = String(data: data, encoding: NSUTF8StringEncoding) else {
285+
throw Error.UnicodeDecodingError(data)
235286
}
236287

288+
return output
289+
}
290+
291+
private func parseEnv(_ env: String) throws -> [String: String] {
237292
var result = [String: String]()
238-
for variable in string.components(separatedBy: "\n") where variable != "" {
239-
guard let range = variable.range(of: "=") else {
240-
XCTFail("Could not parse environment variable: \"\(variable)\"")
241-
continue
293+
for line in env.components(separatedBy: "\n") where line != "" {
294+
guard let range = line.range(of: "=") else {
295+
throw Error.InvalidEnvironmentVariable(line)
242296
}
243-
result[variable.substring(to: range.lowerBound)] = variable.substring(from: range.upperBound)
297+
result[line.substring(to: range.lowerBound)] = line.substring(from: range.upperBound)
244298
}
245299
return result
246300
}
301+
302+

0 commit comments

Comments
 (0)