A simple Swift wrapper around Process
supporting Swift Concurrency and streamed output from stdout
and stderr
.
macOS 10.15+
Add Coquille to your project using Xcode (File > Add Packages...) or by adding it to your project's Package.swift
file:
dependencies: [
.package(url: "https://github.com/alexrozanski/Coquille.git", from: "0.3.0")
]
Coquille exposes its own Process
class which you can interact with to execute commands. Process.run()
is an async
function so you can just await
the exit code:
import Coquille
let process = Process(commandString: "pwd"))
_ = try await process.run() // Prints `pwd` to `stdout`
// Use `command:` for more easily working with variable command-line arguments
let deps = ["numpy", "torch"]
let process = Process(command: .init("python3", arguments: ["-m", "pip", "install"] + deps)))
_ = try await process.run()
By default Process
does not pipe any output from the spawned process to stdout
and stderr
. This can be configured with printStdout
and printStderr
:
import Coquille
let process = Process(commandString: "brew install wget", printStdout: true))
_ = try await process.run() // Pipes standard output to `stdout` but will not pipe error output to `stderr`
You can also pass an OutputHandler
for both stdout and stderr which will stream contents from both:
import Coquille
let process = Process(
commandString: "swift build",
stdout: { stdout in
...
},
stderr: { stderr in
...
})
_ = try await process.run() // Streams standard and error output to the handlers provided to `stdout:` and `stderr:`
// `isSuccess` can be used to test the exit code for success
let hasRuby = (try await Process(commandString: "which ruby").run()).isSuccess
// Use `errorCode` to get a nonzero exit code
if let errorCode = (try await Process(commandString: "swift build").run()).errorCode {
switch errorCode {
case 127:
// Command not found
default:
...
}
}
The main Process.run()
function signature is:
public func run() async throws -> Status
which allows you use Swift Concurrency to execute the subprocess and await
the exit status. However if you want to support cancellation you can use the other run()
function:
public func run(with completionHandler: @escaping ((Status) -> Void)) -> ProcessCancellationHandle
This immediately returns an opaque ProcessCancellationHandle
type which you can call cancel()
on, should you wish to cancel execution, and the process status is delivered through a completionHandler
closure.
Thanks to Ben Chatelain for their blog post on intercepting stdout, used to implement some of the tests in the test suite.