Skip to content

Commit bf15027

Browse files
committed
Add support for currentDirectoryPath in Process class.
This patch adds support for the `currentDirectoryPath` property on `Process` Since `posix_spawn` doesn't make it easy to change the working directory, a call to `chdir` is issued just before the process executes. It looks like `lldb` follows a similar pattern in one of its tools (`tools/darwin-debug/darwin-debug.cpp`).
1 parent f550ea6 commit bf15027

File tree

2 files changed

+38
-1
lines changed

2 files changed

+38
-1
lines changed

Foundation/Process.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,30 @@ open class Process: NSObject {
387387
posix(posix_spawn_file_actions_addclose(&fileActions, fd))
388388
}
389389

390+
let fileManager = FileManager()
391+
let previousDirectoryPath = fileManager.currentDirectoryPath
392+
if !fileManager.changeCurrentDirectoryPath(currentDirectoryPath) {
393+
// Foundation throws an NSException when changing the working directory fails,
394+
// and unfortunately launch() is not marked `throws`, so we get away with a
395+
// fatalError.
396+
switch errno {
397+
case ENOENT:
398+
fatalError("Process: The specified working directory does not exist.")
399+
case EACCES:
400+
fatalError("Process: The specified working directory cannot be accessed.")
401+
default:
402+
fatalError("Process: The specified working directory cannot be set.")
403+
}
404+
}
405+
390406
// Launch
391407

392408
var pid = pid_t()
393409
posix(posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp))
394410

411+
// Reset the previous working directory path.
412+
fileManager.changeCurrentDirectoryPath(previousDirectoryPath)
413+
395414
// Close the write end of the input and output pipes.
396415
if let pipe = standardInput as? Pipe {
397416
pipe.fileHandleForReading.closeFile()

TestFoundation/TestProcess.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class TestProcess : XCTestCase {
2828
("test_pipe_stdin", test_pipe_stdin),
2929
("test_pipe_stdout", test_pipe_stdout),
3030
("test_pipe_stderr", test_pipe_stderr),
31+
("test_current_working_directory", test_current_working_directory),
3132
// disabled for now
3233
// ("test_pipe_stdout_and_stderr_same_pipe", test_pipe_stdout_and_stderr_same_pipe),
3334
("test_file_stdout", test_file_stdout),
@@ -262,6 +263,19 @@ class TestProcess : XCTestCase {
262263
XCTFail("Test failed: \(error)")
263264
}
264265
}
266+
267+
func test_current_working_directory() {
268+
do {
269+
let previousWorkingDirectory = FileManager.default.currentDirectoryPath
270+
271+
// `bash` will not be found if the current working directory is not set correctly.
272+
let _ = try runTask(["bash", "-c", "exit 0"], currentDirectoryPath: "/bin")
273+
274+
XCTAssertEqual(previousWorkingDirectory, FileManager.default.currentDirectoryPath)
275+
} catch let error {
276+
XCTFail("Test failed: \(error)")
277+
}
278+
}
265279
}
266280

267281
private func mkstemp(template: String, body: (FileHandle) throws -> Void) rethrows {
@@ -284,14 +298,18 @@ private enum Error: Swift.Error {
284298
case InvalidEnvironmentVariable(String)
285299
}
286300

287-
private func runTask(_ arguments: [String], environment: [String: String]? = nil) throws -> String {
301+
private func runTask(_ arguments: [String], environment: [String: String]? = nil, currentDirectoryPath: String? = nil) throws -> String {
288302
let process = Process()
289303

290304
var arguments = arguments
291305
process.launchPath = arguments.removeFirst()
292306
process.arguments = arguments
293307
process.environment = environment
294308

309+
if let directoryPath = currentDirectoryPath {
310+
process.currentDirectoryPath = directoryPath
311+
}
312+
295313
let pipe = Pipe()
296314
process.standardOutput = pipe
297315
process.standardError = pipe

0 commit comments

Comments
 (0)