|
1 | 1 | import Foundation |
2 | 2 |
|
| 3 | +#if canImport(Darwin) |
| 4 | + import Darwin |
| 5 | +#endif |
| 6 | + |
3 | 7 | /// A utility for running apps. |
4 | 8 | enum Runner { |
5 | 9 | /// Runs the given app. |
@@ -109,19 +113,87 @@ enum Runner { |
109 | 113 | arguments: [String], |
110 | 114 | environmentVariables: [String: String] |
111 | 115 | ) -> Result<Void, RunnerError> { |
112 | | - let appName = bundle.deletingPathExtension().lastPathComponent |
113 | | - let executable = bundle.appendingPathComponent("Contents/MacOS/\(appName)") |
| 116 | + #if canImport(Darwin) |
| 117 | + let tmp = FileManager.default.temporaryDirectory |
| 118 | + let uuid = UUID().uuidString |
| 119 | + let stdinPipe = tmp.appendingPathComponent("stdin_\(uuid)") |
| 120 | + let stdoutPipe = tmp.appendingPathComponent("stdout_\(uuid)") |
| 121 | + let stderrPipe = tmp.appendingPathComponent("stderr_\(uuid)") |
114 | 122 |
|
115 | | - let process = Process.create( |
116 | | - executable.path, |
117 | | - runSilentlyWhenNotVerbose: false |
118 | | - ) |
119 | | - process.arguments = arguments |
120 | | - process.addEnvironmentVariables(environmentVariables) |
| 123 | + guard |
| 124 | + mkfifo(stdinPipe.path, 0o777) == 0, |
| 125 | + mkfifo(stdoutPipe.path, 0o777) == 0, |
| 126 | + mkfifo(stderrPipe.path, 0o777) == 0 |
| 127 | + else { |
| 128 | + return .failure(.failedToCreateNamedPipes(errno: Int(errno))) |
| 129 | + } |
121 | 130 |
|
122 | | - return process.runAndWait().mapError { error in |
123 | | - return .failedToRunExecutable(error) |
124 | | - } |
| 131 | + let process = Process.create( |
| 132 | + "open", |
| 133 | + arguments: [ |
| 134 | + "-a", bundle.path, |
| 135 | + "-W", |
| 136 | + "--stdin", stdinPipe.path, |
| 137 | + "--stdout", stdoutPipe.path, |
| 138 | + "--stderr", stderrPipe.path, |
| 139 | + "--args", |
| 140 | + ] + arguments, |
| 141 | + runSilentlyWhenNotVerbose: false |
| 142 | + ) |
| 143 | + process.addEnvironmentVariables(environmentVariables) |
| 144 | + |
| 145 | + do { |
| 146 | + try process.run() |
| 147 | + } catch { |
| 148 | + return .failure(.failedToRunExecutable(.failedToRunProcess(error))) |
| 149 | + } |
| 150 | + |
| 151 | + let stdinFlags = fcntl(STDIN_FILENO, F_GETFL, 0) |
| 152 | + guard fcntl(STDIN_FILENO, F_SETFL, stdinFlags | O_NONBLOCK) != -1 else { |
| 153 | + return .failure(.failedToMakeStdinNonBlocking(errno: Int(errno))) |
| 154 | + } |
| 155 | + let appStdin = open(stdinPipe.path, O_WRONLY) |
| 156 | + let appStdout = open(stdoutPipe.path, O_RDONLY | O_NONBLOCK) |
| 157 | + let appStderr = open(stderrPipe.path, O_RDONLY | O_NONBLOCK) |
| 158 | + let events = Int16(POLLIN | POLLHUP) |
| 159 | + var readFDs = [ |
| 160 | + pollfd(fd: appStdout, events: events, revents: 0), |
| 161 | + pollfd(fd: appStderr, events: events, revents: 0), |
| 162 | + pollfd(fd: STDIN_FILENO, events: events, revents: 0), |
| 163 | + ] |
| 164 | + |
| 165 | + var buffer: [UInt8] = Array(repeating: 0, count: 1024) |
| 166 | + while process.isRunning { |
| 167 | + _ = poll(&readFDs, nfds_t(readFDs.count), 0) |
| 168 | + |
| 169 | + let stdinCount = read(STDIN_FILENO, &buffer, buffer.count) |
| 170 | + if stdinCount > 0 { |
| 171 | + print("success") |
| 172 | + write(appStdin, &buffer, stdinCount) |
| 173 | + } |
| 174 | + |
| 175 | + let appStdoutCount = read(appStdout, &buffer, buffer.count) |
| 176 | + if appStdoutCount > 0 { |
| 177 | + print("success") |
| 178 | + write(STDOUT_FILENO, &buffer, appStdoutCount) |
| 179 | + } |
| 180 | + |
| 181 | + let appStderrCount = read(appStderr, &buffer, buffer.count) |
| 182 | + if appStderrCount > 0 { |
| 183 | + print("success") |
| 184 | + write(STDERR_FILENO, &buffer, appStderrCount) |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + let exitStatus = Int(process.terminationStatus) |
| 189 | + if exitStatus != 0 { |
| 190 | + return .failure(.failedToRunExecutable(.nonZeroExitStatus(exitStatus))) |
| 191 | + } else { |
| 192 | + return .success() |
| 193 | + } |
| 194 | + #else |
| 195 | + return .failure(.cannotRunMacOSAppWithoutDarwin) |
| 196 | + #endif |
125 | 197 | } |
126 | 198 |
|
127 | 199 | /// Runs an app on the first connected iOS device. |
|
0 commit comments