Skip to content

Latest commit

 

History

History
302 lines (188 loc) · 9.88 KB

README.md

File metadata and controls

302 lines (188 loc) · 9.88 KB

Subprocess

Subprocess is a cross-platform package for spawning processes in Swift.

Getting Started

To use Subprocess in a SwiftPM project, add it as a package dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/swiftlang/swift-subprocess.git", branch: "main")
]

Then, adding the Subprocess module to your target dependencies:

.target(
    name: "MyTarget",
    dependencies: [
        .product(name: "Subprocess", package: "Subprocess")
    ]
)

On Swift 6.1 and above, Subprocess offers two package traits:

  • SubprocessFoundation: includes a dependency on Foundation and adds extensions on Foundation types like Data. This trait is enabled by default.
  • SubprocessSpan: makes Subprocess’ API, mainly OutputProtocol, RawSpan based. This trait is enabled whenever RawSpan is available and should only be disabled when RawSpan is not available.

Swift Versions

The minimal supported Swift version is Swift 6.0.

To experiment with the SubprocessSpan trait, Swift 6.2 is required. Currently, you can download the Swift 6.2 toolchain (main development snapshot) here.

Feature Overview

Run and Asynchonously Collect Output

The easiest way to spawn a process with Subprocess is to simply run it and await its CollectedResult:

import Subprocess

let result = try await run(.name("ls"))

print(result.processIdentifier) // prints 1234
print(result.terminationStatus) // prints exited(0)

print(result.standardOutput) // prints LICENSE Package.swift ...

Run with Custom Closure

To have more precise control over input and output, you can provide a custom closure that executes while the child process is active. Inside this closure, you have the ability to manage the subprocess’s state (like suspending or terminating it) and stream its standard output and standard error as an AsyncSequence:

import Subprocess

let result = try await run(
    .path("/bin/dd"),
    arguments: ["if=/path/to/document"]
) { execution in
    var contents = ""
    for try await chunk in execution.standardOutput {
        let string = chunk.withUnsafeBytes { String(decoding: $0, as: UTF8.self) }
        contents += string
        if string == "Done" {
            // Stop execution
            await execution.teardown(
                using: [
                    .gracefulShutDown(
                        allowedDurationToNextStep: .seconds(0.5)
                    )
                ]
            )
            return contents
        }
    }
    return contents
}

Running Unmonitored Processes

While Subprocess is designed with Swift’s structural concurrency in mind, it also provides a lower level, synchronous method for launching child processes. However, since Subprocess can’t synchronously monitor child process’s state or handle cleanup, you’ll need to attach a FileDescriptor to each I/O directly. Remember to close the FileDescriptor once you’re finished.

import Subprocess

let input: FileDescriptor = ...

input.closeAfter {
    let pid = try runDetached(.path("/bin/daemon"), input: input)
    // ... other opeartions
}

Customizable Execution

You can set various parameters when running the child process, such as Arguments, Environment, and working directory:

import Subprocess

let result = try await run(
    .path("/bin/ls"),
    arguments: ["-a"],
    // Inherit the environment values from parent process and
    // add `NewKey=NewValue` 
    environment: .inherit.updating(["NewKey": "NewValue"]),
    workingDirectory: "/Users/",
)

Platform Specific Options and “Escape Hatches”

Subprocess provides platform-specific configuration options, like setting uid and gid on Unix and adjusting window style on Windows, through the PlatformOptions struct. Check out the PlatformOptions documentation for a complete list of configurable parameters across different platforms.

PlatformOptions also allows access to platform-specific spawning constructs and customizations via a closure.

import Darwin
import Subprocess

var platformOptions = PlatformOptions()
let intendedWorkingDir = "/path/to/directory"
platformOptions.preSpawnProcessConfigurator = { spawnAttr, fileAttr in
    // Set POSIX_SPAWN_SETSID flag, which implies calls
    // to setsid
    var flags: Int16 = 0
    posix_spawnattr_getflags(&spawnAttr, &flags)
    posix_spawnattr_setflags(&spawnAttr, flags | Int16(POSIX_SPAWN_SETSID))

    // Change the working directory
    intendedWorkingDir.withCString { path in
        _ = posix_spawn_file_actions_addchdir_np(&fileAttr, path)
    }
}

let result = try await run(.path("/bin/exe"), platformOptions: platformOptions)

Flexible Input and Output Configurations

By default, Subprocess:

  • Doesn’t send any input to the child process’s standard input
  • Captures the child process’s standard output as a String, up to 128kB
  • Ignores the child process’s standard error

You can tailor how Subprocess handles the standard input, standard output, and standard error by setting the input, output, and error parameters:

let content = "Hello Subprocess"

// Send "Hello Subprocess" to the standard input of `cat`
let result = try await run(.name("cat"), input: .string(content, using: UTF8.self))

// Collect both standard error and standard output as Data
let result = try await run(.name("cat"), output: .data, error: .data)

Subprocess supports these input options:

NoInput

This option means no input is sent to the subprocess.

Use it by setting .none for input.

FileDescriptorInput

This option reads input from a specified FileDescriptor. If closeAfterSpawningProcess is set to true, the subprocess will close the file descriptor after spawning. If false, you are responsible for closing it, even if the subprocess fails to spawn.

Use it by setting .fileDescriptor(closeAfterSpawningProcess:) for input.

StringInput

This option reads input from a type conforming to StringProtocol using the specified encoding.

Use it by setting .string(using:) for input.

ArrayInput

This option reads input from an array of UInt8.

Use it by setting .array for input.

DataInput (available with SubprocessFoundation trait)

This option reads input from a given Data.

Use it by setting .data for input.

DataSequenceInput (available with SubprocessFoundation trait)

This option reads input from a sequence of Data.

Use it by setting .sequence for input.

DataAsyncSequenceInput (available with SubprocessFoundation trait)

This option reads input from an async sequence of Data.

Use it by setting .asyncSequence for input.


Subprocess also supports these output options:

DiscardedOutput

This option means the Subprocess won’t collect or redirect output from the child process.

Use it by setting .discarded for input or error.

FileDescriptorOutput

This option writes output to a specified FileDescriptor. You can choose to have the Subprocess close the file descriptor after spawning.

Use it by setting .fileDescriptor(closeAfterSpawningProcess:) for input or error.

StringOutput

This option collects output as a String with the given encoding.

Use it by setting .string or .string(limit:encoding:) for input or error.

BytesOutput

This option collects output as [UInt8].

Use it by setting .bytes or .bytes(limit:) for input or error.

SequenceOutput:

This option redirects the child output to the .standardOutput or .standardError property of Execution. It’s only for the run() family that takes a custom closure.

Cross-platform support

Subprocess works on macOS, Linux, and Windows, with feature parity on all platforms as well as platform-specific options for each.

The table below describes the current level of support that Subprocess has for various platforms:

Platform Support Status
macOS Supported
iOS Not supported
watchOS Not supported
tvOS Not supported
visionOS Not supported
Ubuntu 22.04 Supported
Windows Supported

(back to top)

Documentation

The latest API documentation can be viewed by running the following command:

swift package --disable-sandbox preview-documentation --target Subprocess

(back to top)

Contributing to Subprocess

Subprocess is part of the Foundation project. Discussion and evolution take place on Swift Foundation Forum.

If you find something that looks like a bug, please open a Bug Report! Fill out as many details as you can.

(back to top)

Code of Conduct

Like all Swift.org projects, we would like the Subprocess project to foster a diverse and friendly community. We expect contributors to adhere to the Swift.org Code of Conduct.

(back to top)

Contact information

The Foundation Workgroup communicates with the broader Swift community using the forum for general discussions.

The workgroup can also be contacted privately by messaging @foundation-workgroup on the Swift Forums.

(back to top)