Hi all,
I would like to pitch a new API, Subprocess
, that that will eventually replace Process
as the canonical way to launch a process in Swift
. Please let me know if you have any questions/comments/thoughts!
Introducing Swift Subprocess
- Proposal: SF-NNNN
- Authors: Charles Hu
- Review Manager: TBD
- Status: Draft
- Bugs: rdar://118127512, apple/swift-foundation#309
Introduction / Motivation
As Swift establishes itself as a general-purpose language for both compiled and scripting use cases, one persistent pain point for developers is process creation. The existing Foundation API for spawning a process, NSTask
, originated in Objective-C. It was subsequently renamed to Process
in Swift. As the language has continued to evolve, Process
has not kept up. It lacks support for async/await
, makes extensive use of completion handlers, and uses Objective-C exceptions to indicate developer error. This proposal introduces a new type called Subprocess
, which addresses the ergonomic shortcomings of Process
and enhances the experience of using Swift for scripting.
Proposed Solution
We propose a new type, struct Subprocess
, that will eventually replace Process
as the canonical way to launch a process in Swift
.
Here's a simple usage Subprocess
:
#!/usr/bin/swift
import FoundationEssentials
let gitResult = try await Subprocess.run( // <- 0
executing: .named("git"), // <- 1
arguments: ["diff", "--name-only"]
)
if let output = gitResult.standardOutput { // <- 2
print("Output: \(Strint(data: output, encoding: .utf8))")
}
Let's break down the example above:
Subprocess
is constructed entirely on theasync/await
paradigm. Therun()
method utilizesawait
to allow the child process to finish, asynchronously returning anExecutionResult
. Additionally, there is an closure based overload ofrun()
that offers more granulated control, which will be discussed later.- There are two primary ways to configure the executable being run:
- The default approach, recommended for most developers, is
.at(FilePath)
. This allows developers to specify a full path to an executable. - Alternatively, developers can use
.named(String)
to instructSubprocess
to look up the executable path based on heuristics such as the$PATH
environment variable.
- The default approach, recommended for most developers, is
- The standard output and error are delivered either via
AsyncSequence
orData
.
I've posted the full pitch including the detailed design here on the swift-foundation repo. Check out the full pitch to read more info about the proposed design.
Trying it out
You can find a experimental (WIP) implementation here: swift-experimental-subprocess
Feel free to play around with it and let me know what you think!