diff --git a/CoreFoundation/Base.subproj/CFPlatform.c b/CoreFoundation/Base.subproj/CFPlatform.c index 81b6a9a822..300d42a272 100644 --- a/CoreFoundation/Base.subproj/CFPlatform.c +++ b/CoreFoundation/Base.subproj/CFPlatform.c @@ -1546,4 +1546,374 @@ CF_CROSS_PLATFORM_EXPORT void *_CFReallocf(void *ptr, size_t size) { #endif } +#if TARGET_OS_ANDROID + +#include +#include + +// Android doesn't provide posix_spawn APIs until recent API level, so we cannot +// depend on them, but we can imitate the API, and perform the same work. + +static pthread_once_t posixSpawnOnce = PTHREAD_ONCE_INIT; +static _CFPosixSpawnFileActionsRef (*_CFPosixSpawnFileActionsAllocImpl)(void); +static int (*_CFPosixSpawnFileActionsInitImpl)(_CFPosixSpawnFileActionsRef); +static int (*_CFPosixSpawnFileActionsDestroyImpl)(_CFPosixSpawnFileActionsRef); +static void (*_CFPosixSpawnFileActionsDeallocImpl)(_CFPosixSpawnFileActionsRef); +static int (*_CFPosixSpawnFileActionsAddDup2Impl)(_CFPosixSpawnFileActionsRef, int, int); +static int (*_CFPosixSpawnFileActionsAddCloseImpl)(_CFPosixSpawnFileActionsRef, int); +static int (*_CFPosixSpawnImpl)(pid_t *_CF_RESTRICT, const char *_CF_RESTRICT, _CFPosixSpawnFileActionsRef, _CFPosixSpawnAttrRef _Nullable _CF_RESTRICT, char *_Nullable const[_Nullable _CF_RESTRICT], char *_Nullable const[_Nullable _CF_RESTRICT]); + +static _CFPosixSpawnFileActionsRef _CFPosixSpawnFileActionsAllocImplPost28() { + _CFPosixSpawnFileActionsRef actions = malloc(sizeof(posix_spawn_file_actions_t)); + CFAssert(actions != NULL, __kCFLogAssertion, "malloc failed"); + return actions; +} + +static void _CFPosixSpawnFileActionsDeallocImplBoth(_CFPosixSpawnFileActionsRef file_actions) { + free(file_actions); +} + +enum _CFPosixSpawnFileActionTypePre28 { + _CFPosixSpawnFileActionDup2Pre28, + _CFPosixSpawnFileActionClosePre28, +}; + +struct _CFPosixSpawnFileActionPre28 { + enum _CFPosixSpawnFileActionTypePre28 type; + union { + struct { + int filedes; + int newfiledes; + } dup2Action; + struct { + int filedes; + } closeAction; + }; +}; + +struct _CFPosixSpawnFileActionsPre28 { + struct _CFPosixSpawnFileActionPre28 *actions; + size_t actionsCount; + size_t actionsCapacity; + int32_t isValid; +}; + +static const int32_t _CFPosixSpawnFileActionsPre28Valid = 0x600D600D; + +static _CFPosixSpawnFileActionsRef _CFPosixSpawnFileActionsAllocImplPre28() { + _CFPosixSpawnFileActionsRef actions = calloc(1, sizeof(struct _CFPosixSpawnFileActionsPre28)); + CFAssert(actions != NULL, __kCFLogAssertion, "malloc failed"); + return actions; +} + +static int _CFPosixSpawnFileActionsInitImplPre28(_CFPosixSpawnFileActionsRef file_actions) { + if (file_actions == NULL) { + return EINVAL; + } + + struct _CFPosixSpawnFileActionsPre28 *actions = (struct _CFPosixSpawnFileActionsPre28 *)file_actions; + + actions->actions = malloc(8 * sizeof(struct _CFPosixSpawnFileActionPre28)); + if (actions->actions == NULL) { + return ENOMEM; + } + actions->actionsCount = 0; + actions->actionsCapacity = 8; + + actions->isValid = _CFPosixSpawnFileActionsPre28Valid; + + return 0; +} + +static int _CFPosixSpawnFileActionsDestroyImplPre28(_CFPosixSpawnFileActionsRef file_actions) { + if (file_actions == NULL) { + return EINVAL; + } + + struct _CFPosixSpawnFileActionsPre28 *actions = (struct _CFPosixSpawnFileActionsPre28 *)file_actions; + if (actions->isValid != _CFPosixSpawnFileActionsPre28Valid) { + return EINVAL; + } + + free(actions->actions); + actions->actionsCount = 0; + actions->actionsCapacity = 0; + + actions->isValid = 0; + + return 0; +} + +static int _CFPosixSpawnFileActionsAddDup2ImplPre28(_CFPosixSpawnFileActionsRef file_actions, int filedes, int newfiledes) { + if (file_actions == NULL) { + return EINVAL; + } + + if (filedes < 0 || newfiledes < 0) { + return EBADF; + } + + struct _CFPosixSpawnFileActionsPre28 *actions = (struct _CFPosixSpawnFileActionsPre28 *)file_actions; + if (actions->isValid != _CFPosixSpawnFileActionsPre28Valid) { + return EINVAL; + } + + if (actions->actionsCount == actions->actionsCapacity) { + struct _CFPosixSpawnFileActionPre28 *newActions = realloc(actions->actions, actions->actionsCapacity * 2); + if (newActions == NULL) { + return ENOMEM; + } + actions->actions = newActions; + actions->actionsCapacity *= 2; + } + + actions->actions[actions->actionsCount++] = (struct _CFPosixSpawnFileActionPre28) { + .type = _CFPosixSpawnFileActionDup2Pre28, + .dup2Action = { + .filedes = filedes, + .newfiledes = newfiledes + } + }; + + return 0; +} + +static int _CFPosixSpawnFileActionsAddCloseImplPre28(_CFPosixSpawnFileActionsRef file_actions, int filedes) { + if (file_actions == NULL) { + return EINVAL; + } + + if (filedes < 0) { + return EBADF; + } + + struct _CFPosixSpawnFileActionsPre28 *actions = (struct _CFPosixSpawnFileActionsPre28 *)file_actions; + if (actions->isValid != _CFPosixSpawnFileActionsPre28Valid) { + return EINVAL; + } + + if (actions->actionsCount == actions->actionsCapacity) { + struct _CFPosixSpawnFileActionPre28 *newActions = realloc(actions->actions, actions->actionsCapacity * 2); + if (newActions == NULL) { + return ENOMEM; + } + actions->actions = newActions; + actions->actionsCapacity *= 2; + } + + actions->actions[actions->actionsCount++] = (struct _CFPosixSpawnFileActionPre28) { + .type = _CFPosixSpawnFileActionClosePre28, + .closeAction = { + .filedes = filedes + } + }; + + return 0; +} + +static int _CFPosixSpawnImplPre28(pid_t *_CF_RESTRICT pid, const char *_CF_RESTRICT path, _CFPosixSpawnFileActionsRef file_actions, _CFPosixSpawnAttrRef _Nullable _CF_RESTRICT attrp, char *_Nullable const argv[_Nullable _CF_RESTRICT], char *_Nullable const envp[_Nullable _CF_RESTRICT]) { + // TODO: We completely ignore attrp, because at the moment, the only + // invocation doesn't pass a value. + if (attrp != NULL) { + return EINVAL; + } + + struct _CFPosixSpawnFileActionsPre28 *actions = (struct _CFPosixSpawnFileActionsPre28 *)file_actions; + if (actions != NULL && actions->isValid != _CFPosixSpawnFileActionsPre28Valid) { + return EINVAL; + } + + // Block signals during this fork/execv dance. + sigset_t signalSet; + sigfillset(&signalSet); + sigset_t oldMask; + if (sigprocmask(SIG_BLOCK, &signalSet, &oldMask) != 0) { + CFAssert1(FALSE, __kCFLogAssertion, "sigprocmask() failed: %d", errno); + } + + pid_t forkPid = fork(); + if (forkPid != 0) { + // This is the parent. fork call might have been successful or not. + + // Unblock signals. + if (sigprocmask(SIG_SETMASK, &oldMask, NULL) != 0) { + CFAssert1(FALSE, __kCFLogAssertion, "sigprocmask() failed: %d", errno); + } + + if (forkPid < 0) { + return forkPid; + } + + if (pid != NULL) { + *pid = forkPid; + } + + return 0; + } + + // This is the child. + + // Clean up the parent signal handlers + for (int idx = 1; idx < NSIG; idx++) { + // Seems that SIGKILL/SIGSTOP are sometimes silently ignored, and + // sometimes return EINVAL. Since one cannot change the handlers anyway, + // skip them. + if (idx == SIGKILL || idx == SIGSTOP) { + continue; + } + + struct sigaction sigAction; + if (sigaction(idx, NULL, &sigAction) != 0) { + exit(127); + } + + if (sigAction.sa_handler != SIG_IGN) { + sigAction.sa_handler = SIG_DFL; + if (sigaction(idx, &sigAction, NULL) != 0) { + exit(127); + } + } + } + + // Perform the actions + if (actions != NULL) { + for (size_t idx = 0; idx < actions->actionsCount; idx++) { + struct _CFPosixSpawnFileActionPre28 *action = &(actions->actions[idx]); + if (action->type == _CFPosixSpawnFileActionDup2Pre28) { + if (dup2(action->dup2Action.filedes, action->dup2Action.newfiledes) < 0) { + exit(127); + } + } else if (actions->actions[idx].type == _CFPosixSpawnFileActionClosePre28) { + if (close(action->closeAction.filedes) != 0) { + exit(127); + } + } + } + } + + // Unblock the signals + if (sigprocmask(SIG_SETMASK, &oldMask, NULL) != 0) { + CFAssert1(FALSE, __kCFLogAssertion, "sigprocmask() failed: %d", errno); + } + + // If execv fails, we will simply exit 127 as the standard says. + execve(path, argv, envp ?: environ); + exit(127); + // no need for return here +} + +static void _CFPosixSpawnInitializeCallback() { + // Let's check if the posix_spawn is present. + (void)dlerror(); // Clean up the error. + _CFPosixSpawnImpl = (int (*)(pid_t *_CF_RESTRICT, const char *_CF_RESTRICT, void *, void *_CF_RESTRICT, char *const *_CF_RESTRICT, char *const *_CF_RESTRICT))dlsym(RTLD_DEFAULT, "posix_spawn"); + char *dlsymError = dlerror(); + CFAssert1(dlsymError == NULL, __kCFLogAssertion, "dlsym failed: %s", dlsymError); + if (_CFPosixSpawnImpl != NULL) { + // posix_spawn_fn is available, so use it + _CFPosixSpawnFileActionsAllocImpl = _CFPosixSpawnFileActionsAllocImplPost28; + _CFPosixSpawnFileActionsDeallocImpl = _CFPosixSpawnFileActionsDeallocImplBoth; + + _CFPosixSpawnFileActionsInitImpl = (int (*)(void *))dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_init"); + dlsymError = dlerror(); + CFAssert1(_CFPosixSpawnFileActionsInitImpl != NULL, __kCFLogAssertion, "loading posix_spawn_file_actions_init failed: %s", dlsymError); + + _CFPosixSpawnFileActionsDestroyImpl = (int (*)(void *))dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_destroy"); + dlsymError = dlerror(); + CFAssert1(_CFPosixSpawnFileActionsDestroyImpl != NULL, __kCFLogAssertion, "loading posix_spawn_file_actions_destroy failed: %s", dlsymError); + + _CFPosixSpawnFileActionsAddDup2Impl = (int (*)(void *, int, int))dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_adddup2"); + dlsymError = dlerror(); + CFAssert1(_CFPosixSpawnFileActionsAddDup2Impl != NULL, __kCFLogAssertion, "loading posix_spawn_file_actions_adddup2 failed: %s", dlsymError); + + _CFPosixSpawnFileActionsAddCloseImpl = (int (*)(void *, int))dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addclose"); + dlsymError = dlerror(); + CFAssert1(_CFPosixSpawnFileActionsAddCloseImpl != NULL, __kCFLogAssertion, "loading posix_spawn_file_actions_addclose failed: %s", dlsymError); + } else { + // posix_spawn_fn is not available, setup our workaround + _CFPosixSpawnFileActionsAllocImpl = _CFPosixSpawnFileActionsAllocImplPre28; + _CFPosixSpawnFileActionsDeallocImpl = _CFPosixSpawnFileActionsDeallocImplBoth; + _CFPosixSpawnFileActionsInitImpl = _CFPosixSpawnFileActionsInitImplPre28; + _CFPosixSpawnFileActionsDestroyImpl = _CFPosixSpawnFileActionsDestroyImplPre28; + _CFPosixSpawnFileActionsAddDup2Impl = _CFPosixSpawnFileActionsAddDup2ImplPre28; + _CFPosixSpawnFileActionsAddCloseImpl = _CFPosixSpawnFileActionsAddCloseImplPre28; + _CFPosixSpawnImpl = _CFPosixSpawnImplPre28; + } +} + +static void _CFPosixSpawnInitialize() { + int r = pthread_once(&posixSpawnOnce, _CFPosixSpawnInitializeCallback); + CFAssert(r == 0, __kCFLogAssertion, "pthread_once failed"); +} + +CF_EXPORT _CFPosixSpawnFileActionsRef _CFPosixSpawnFileActionsAlloc() { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnFileActionsAllocImpl(); +} + +CF_EXPORT int _CFPosixSpawnFileActionsInit(_CFPosixSpawnFileActionsRef file_actions) { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnFileActionsInitImpl(file_actions); +} + +CF_EXPORT int _CFPosixSpawnFileActionsDestroy(_CFPosixSpawnFileActionsRef file_actions) { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnFileActionsDestroyImpl(file_actions); +} + +CF_EXPORT void _CFPosixSpawnFileActionsDealloc(_CFPosixSpawnFileActionsRef file_actions) { + _CFPosixSpawnInitialize(); + _CFPosixSpawnFileActionsDeallocImpl(file_actions); +} + +CF_EXPORT int _CFPosixSpawnFileActionsAddDup2(_CFPosixSpawnFileActionsRef file_actions, int filedes, int newfiledes) { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnFileActionsAddDup2Impl(file_actions, filedes, newfiledes); +} + +CF_EXPORT int _CFPosixSpawnFileActionsAddClose(_CFPosixSpawnFileActionsRef file_actions, int filedes) { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnFileActionsAddCloseImpl(file_actions, filedes); +} + +CF_EXPORT int _CFPosixSpawn(pid_t *_CF_RESTRICT pid, const char *_CF_RESTRICT path, _CFPosixSpawnFileActionsRef file_actions, _CFPosixSpawnAttrRef _Nullable _CF_RESTRICT attrp, char *_Nullable const argv[_Nullable _CF_RESTRICT], char *_Nullable const envp[_Nullable _CF_RESTRICT]) { + _CFPosixSpawnInitialize(); + return _CFPosixSpawnImpl(pid, path, file_actions, attrp, argv, envp); +} + +#elif !TARGET_OS_WIN32 + +#include + +CF_EXPORT _CFPosixSpawnFileActionsRef _CFPosixSpawnFileActionsAlloc() { + _CFPosixSpawnFileActionsRef actions = malloc(sizeof(posix_spawn_file_actions_t)); + CFAssert(actions != NULL, __kCFLogAssertion, "malloc failed"); + return actions; +} + +CF_EXPORT int _CFPosixSpawnFileActionsInit(_CFPosixSpawnFileActionsRef file_actions) { + return posix_spawn_file_actions_init((posix_spawn_file_actions_t *)file_actions); +} + +CF_EXPORT int _CFPosixSpawnFileActionsDestroy(_CFPosixSpawnFileActionsRef file_actions) { + return posix_spawn_file_actions_destroy((posix_spawn_file_actions_t *)file_actions); +} + +CF_EXPORT void _CFPosixSpawnFileActionsDealloc(_CFPosixSpawnFileActionsRef file_actions) { + free(file_actions); +} + +CF_EXPORT int _CFPosixSpawnFileActionsAddDup2(_CFPosixSpawnFileActionsRef file_actions, int filedes, int newfiledes) { + return posix_spawn_file_actions_adddup2((posix_spawn_file_actions_t *)file_actions, filedes, newfiledes); +} + +CF_EXPORT int _CFPosixSpawnFileActionsAddClose(_CFPosixSpawnFileActionsRef file_actions, int filedes) { + return posix_spawn_file_actions_addclose((posix_spawn_file_actions_t *)file_actions, filedes); +} + +CF_EXPORT int _CFPosixSpawn(pid_t *_CF_RESTRICT pid, const char *_CF_RESTRICT path, _CFPosixSpawnFileActionsRef file_actions, _CFPosixSpawnAttrRef _Nullable _CF_RESTRICT attrp, char *_Nullable const argv[_Nullable _CF_RESTRICT], char *_Nullable const envp[_Nullable _CF_RESTRICT]) { + return posix_spawn(pid, path, (posix_spawn_file_actions_t *)file_actions, (posix_spawnattr_t *)attrp, argv, envp); +} + +#endif + #endif diff --git a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h index e1ec82e751..6e31c634e8 100644 --- a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h +++ b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h @@ -62,9 +62,11 @@ #include #include #include +#include #elif TARGET_OS_LINUX #include #include +#include #if __GLIBC_PREREQ(2, 28) == 0 // required for statx() system call, glibc >=2.28 wraps the kernel function @@ -651,6 +653,18 @@ typedef struct _REPARSE_DATA_BUFFER { } REPARSE_DATA_BUFFER; #endif +#if !TARGET_OS_WIN32 +typedef void * _CFPosixSpawnFileActionsRef; +typedef void * _CFPosixSpawnAttrRef; +CF_EXPORT _CFPosixSpawnFileActionsRef _CFPosixSpawnFileActionsAlloc(void); +CF_EXPORT int _CFPosixSpawnFileActionsInit(_CFPosixSpawnFileActionsRef file_actions); +CF_EXPORT int _CFPosixSpawnFileActionsDestroy(_CFPosixSpawnFileActionsRef file_actions); +CF_EXPORT void _CFPosixSpawnFileActionsDealloc(_CFPosixSpawnFileActionsRef file_actions); +CF_EXPORT int _CFPosixSpawnFileActionsAddDup2(_CFPosixSpawnFileActionsRef file_actions, int filedes, int newfiledes); +CF_EXPORT int _CFPosixSpawnFileActionsAddClose(_CFPosixSpawnFileActionsRef file_actions, int filedes); +CF_EXPORT int _CFPosixSpawn(pid_t *_CF_RESTRICT pid, const char *_CF_RESTRICT path, _CFPosixSpawnFileActionsRef file_actions, _CFPosixSpawnAttrRef _Nullable _CF_RESTRICT attrp, char *_Nullable const argv[_Nullable _CF_RESTRICT], char *_Nullable const envp[_Nullable _CF_RESTRICT]); +#endif + _CF_EXPORT_SCOPE_END #endif /* __COREFOUNDATION_FORSWIFTFOUNDATIONONLY__ */ diff --git a/Foundation/Process.swift b/Foundation/Process.swift index 18b1707f23..e2c4078ec2 100644 --- a/Foundation/Process.swift +++ b/Foundation/Process.swift @@ -7,7 +7,6 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -#if !os(Android) // not available import CoreFoundation extension Process { @@ -287,6 +286,8 @@ open class Process: NSObject { default: fatalError("Process: The specified working directory cannot be set.") } + } else { + fatalError(String(describing: nserror)) } } catch { fatalError(String(describing: error)) @@ -661,7 +662,7 @@ open class Process: NSObject { } var taskSocketPair : [Int32] = [0, 0] -#if os(macOS) || os(iOS) +#if os(macOS) || os(iOS) || os(Android) socketpair(AF_UNIX, SOCK_STREAM, 0, &taskSocketPair) #else socketpair(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0, &taskSocketPair) @@ -738,14 +739,12 @@ open class Process: NSObject { let source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0) CFRunLoopAddSource(managerThreadRunLoop?._cfRunLoop, source, kCFRunLoopDefaultMode) - // file_actions - #if os(macOS) || os(iOS) || CYGWIN - var fileActions: posix_spawn_file_actions_t? = nil - #else - var fileActions: posix_spawn_file_actions_t = posix_spawn_file_actions_t() - #endif - posix(posix_spawn_file_actions_init(&fileActions)) - defer { posix_spawn_file_actions_destroy(&fileActions) } + var fileActions = _CFPosixSpawnFileActionsAlloc() + posix(_CFPosixSpawnFileActionsInit(fileActions)) + defer { + _CFPosixSpawnFileActionsDestroy(fileActions) + _CFPosixSpawnFileActionsDealloc(fileActions) + } // File descriptors to duplicate in the child process. This allows // output redirection to NSPipe or NSFileHandle. @@ -820,10 +819,10 @@ open class Process: NSObject { } for (new, old) in adddup2 { - posix(posix_spawn_file_actions_adddup2(&fileActions, old, new)) + posix(_CFPosixSpawnFileActionsAddDup2(fileActions, old, new)) } for fd in addclose { - posix(posix_spawn_file_actions_addclose(&fileActions, fd)) + posix(_CFPosixSpawnFileActionsAddClose(fileActions, fd)) } let fileManager = FileManager() @@ -839,7 +838,7 @@ open class Process: NSObject { // Launch var pid = pid_t() - guard posix_spawn(&pid, launchPath, &fileActions, nil, argv, envp) == 0 else { + guard _CFPosixSpawn(&pid, launchPath, fileActions, nil, argv, envp) == 0 else { throw _NSErrorWithErrno(errno, reading: true, path: launchPath) } @@ -1027,11 +1026,10 @@ extension Process { public static let didTerminateNotification = NSNotification.Name(rawValue: "NSTaskDidTerminateNotification") } -private func posix(_ code: Int32) { +private func posix(_ code: Int32, file: StaticString = #file, line: UInt = #line) { switch code { case 0: return - case EBADF: fatalError("POSIX command failed with error: \(code) -- EBADF") - default: fatalError("POSIX command failed with error: \(code)") + case EBADF: fatalError("POSIX command failed with error: \(code) -- EBADF", file: file, line: line) + default: fatalError("POSIX command failed with error: \(code)", file: file, line: line) } } -#endif diff --git a/TestFoundation/TestProcess.swift b/TestFoundation/TestProcess.swift index 49dc5de3b3..935a7c3735 100644 --- a/TestFoundation/TestProcess.swift +++ b/TestFoundation/TestProcess.swift @@ -7,7 +7,6 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // -#if !os(Android) class TestProcess : XCTestCase { func test_exit0() throws { @@ -21,7 +20,7 @@ class TestProcess : XCTestCase { } XCTAssertEqual(executableURL.path, process.launchPath) process.arguments = ["--exit", "0"] - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 0) @@ -33,7 +32,7 @@ class TestProcess : XCTestCase { process.executableURL = xdgTestHelperURL() process.arguments = ["--exit", "1"] - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 1) XCTAssertEqual(process.terminationReason, .exit) @@ -44,7 +43,7 @@ class TestProcess : XCTestCase { process.executableURL = xdgTestHelperURL() process.arguments = ["--exit", "100"] - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 100) XCTAssertEqual(process.terminationReason, .exit) @@ -55,7 +54,7 @@ class TestProcess : XCTestCase { process.executableURL = xdgTestHelperURL() process.arguments = ["--sleep", "2"] - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 0) XCTAssertEqual(process.terminationReason, .exit) @@ -65,7 +64,7 @@ class TestProcess : XCTestCase { let process = Process() process.executableURL = xdgTestHelperURL() process.arguments = ["--signal-self", SIGTERM.description] - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, SIGTERM) XCTAssertEqual(process.terminationReason, .uncaughtSignal) @@ -81,7 +80,7 @@ class TestProcess : XCTestCase { let inputPipe = Pipe() process.standardInput = inputPipe - try? process.run() + try process.run() inputPipe.fileHandleForWriting.write("Hello, 🐶.\n".data(using: .utf8)!) // Close the input pipe to send EOF to cat. @@ -107,7 +106,7 @@ class TestProcess : XCTestCase { process.standardOutput = pipe process.standardError = nil - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 0) @@ -129,7 +128,7 @@ class TestProcess : XCTestCase { let errorPipe = Pipe() process.standardError = errorPipe - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 1) @@ -156,7 +155,14 @@ class TestProcess : XCTestCase { // Clear the environment to stop the malloc debug flags used in Xcode debug being // set in the subprocess. process.environment = [:] - try? process.run() +#if os(Android) + // In Android, we have to provide at least an LD_LIBRARY_PATH, or + // xdgTestHelper will not be able to find the Swift libraries. + if let ldLibraryPath = ProcessInfo.processInfo.environment["LD_LIBRARY_PATH"] { + process.environment?["LD_LIBRARY_PATH"] = ldLibraryPath + } +#endif + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 1) @@ -185,7 +191,7 @@ class TestProcess : XCTestCase { process.standardOutput = handle - try? process.run() + try process.run() process.waitUntilExit() XCTAssertEqual(process.terminationStatus, 0) @@ -242,10 +248,15 @@ class TestProcess : XCTestCase { } func test_current_working_directory() throws { - var tmpDir = NSTemporaryDirectory() - if let lastChar = tmpDir.last, lastChar == "/" || lastChar == "\\" { - tmpDir.removeLast() - } + let tmpDir = { () -> String in + // NSTemporaryDirectory might return a final slash, but + // FileManager.currentDirectoryPath seems to avoid it. + var dir = NSTemporaryDirectory() + if (dir.hasSuffix("/") && dir != "/") || dir.hasSuffix("\\") { + dir.removeLast() + } + return dir + }() let fm = FileManager.default let previousWorkingDirectory = fm.currentDirectoryPath @@ -687,7 +698,16 @@ internal func runTask(_ arguments: [String], environment: [String: String]? = ni process.arguments = arguments // Darwin Foundation doesnt allow .environment to be set to nil although the documentation // says it is an optional. https://developer.apple.com/documentation/foundation/process/1409412-environment - if let e = environment { + if var e = environment { +#if os(Android) + // In Android, we have to provide at least an LD_LIBRARY_PATH, or + // xdgTestHelper will not be able to find the Swift libraries. + if e["LD_LIBRARY_PATH"] == nil { + if let ldLibraryPath = ProcessInfo.processInfo.environment["LD_LIBRARY_PATH"] { + e["LD_LIBRARY_PATH"] = ldLibraryPath + } + } +#endif process.environment = e } @@ -751,9 +771,16 @@ private func parseEnv(_ env: String) throws -> [String: String] { guard let range = line.range(of: "=") else { throw Error.InvalidEnvironmentVariable(line) } - result[String(line[..