Skip to content

Commit 0755556

Browse files
committed
Update macOS runner to use the 'open' command and named pipes to ensure apps open in the foreground while redirecting io to the terminal
1 parent afa005a commit 0755556

File tree

2 files changed

+93
-11
lines changed

2 files changed

+93
-11
lines changed

Sources/swift-bundler/Bundler/Runner/Runner.swift

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import Foundation
22

3+
#if canImport(Darwin)
4+
import Darwin
5+
#endif
6+
37
/// A utility for running apps.
48
enum Runner {
59
/// Runs the given app.
@@ -109,19 +113,87 @@ enum Runner {
109113
arguments: [String],
110114
environmentVariables: [String: String]
111115
) -> 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)")
114122

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+
}
121130

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
125197
}
126198

127199
/// Runs an app on the first connected iOS device.

Sources/swift-bundler/Bundler/Runner/RunnerError.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ enum RunnerError: LocalizedError {
99
case failedToParseEnvironmentFileEntry(line: String)
1010
case failedToRunOnIOSSimulator(SimulatorManagerError)
1111
case failedToRunOnVisionOSSimulator(SimulatorManagerError)
12+
case cannotRunMacOSAppWithoutDarwin
13+
case failedToCreateNamedPipes(errno: Int)
14+
case failedToMakeStdinNonBlocking(errno: Int)
1215

1316
var errorDescription: String? {
1417
switch self {
@@ -32,6 +35,13 @@ enum RunnerError: LocalizedError {
3235
return "Failed to run app on iOS simulator: \(error.localizedDescription)"
3336
case let .failedToRunOnVisionOSSimulator(error):
3437
return "Failed to run app on visionOS simulator: \(error.localizedDescription)"
38+
case .cannotRunMacOSAppWithoutDarwin:
39+
return
40+
"Cannot run a macOS app without Darwin available (this error should never occur, please open an issue on GitHub)"
41+
case let .failedToCreateNamedPipes(errno):
42+
return "Failed to create named pipes for IO redirection (errno \(errno))"
43+
case let .failedToMakeStdinNonBlocking(errno):
44+
return "Failed to make stdin non-blocking (errno \(errno))"
3545
}
3646
}
3747
}

0 commit comments

Comments
 (0)