mirror of
https://github.com/apple/swift-foundation.git
synced 2025-04-20 13:06:34 +08:00
Compare commits
3 Commits
d17367df56
...
caedc92170
Author | SHA1 | Date | |
---|---|---|---|
|
caedc92170 | ||
|
2f65d91455 | ||
|
56d57fbf9c |
@ -96,8 +96,14 @@
|
|||||||
- `Executable`:
|
- `Executable`:
|
||||||
- Renamed `.named` to `.name`.
|
- Renamed `.named` to `.name`.
|
||||||
- Renamed `.at` to `.path`.
|
- Renamed `.at` to `.path`.
|
||||||
- Split `Subprocess` into two modules: `Subprocess` with no `Foundation` dependency and `SubprocessFoundation` with `Foundation` dependency
|
- Split `Subprocess` into main and `SubprocessFoundation` Traits:
|
||||||
|
- `SubprocessFoundation` traits adds `Foundation` dependency and interop.
|
||||||
- Introduce `struct Buffer`
|
- Introduce `struct Buffer`
|
||||||
|
- **v8**: Removing `ManagedInputProtocol` and `ManagedOutputProtocol`
|
||||||
|
- Revise `InputProtocol` and `OutputProtocol` to not expose `FileDescriptor` directly
|
||||||
|
- Added `SubprocessSpan` trait
|
||||||
|
- Removed the opaque `Pipe`
|
||||||
|
- Introduce a cross platform TeardownStep.gracefulShutDown(alloweDurationToNextStep:) and add Windows support
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@ -208,9 +214,13 @@ The latest API documentation can be viewed by running the following command:
|
|||||||
swift package --disable-sandbox preview-documentation --target Subprocess
|
swift package --disable-sandbox preview-documentation --target Subprocess
|
||||||
```
|
```
|
||||||
|
|
||||||
### `Subprocess` and `SubprocessFoundation` Modules
|
### `SubprocessFoundation` and `SubprocessSpan` Traits
|
||||||
|
|
||||||
Within this package, we propose two modules: `Subprocess` and `SubprocessFoundation`. `Subprocess` serves as the “core” module, relying solely on `swift-system` and the standard library. `SubprocessFoundation` extends `Subprocess` by depending on and incorporating types from `Foundation`.
|
The core `Subprocess` package is designed to only depend the standard library and [swift-system](https://github.com/apple/swift-system) (for `FileDescriptor` and `FilePath`). Starting with Swift 6.1 or later, we propose using the new [`traits` feature](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0450-swiftpm-package-traits.md) to introduce a `SubprocessFoundation` trait, which will be on by default. When this trait is on, `Subprocess` includes a dependency on `Foundation` and adds extensions on Foundation types like `Data`.
|
||||||
|
|
||||||
|
We also propose a `SubprocessSpan` trait that makes Subprocess’ API, mainly `OutputProtocol`, `RawSpan` based. This trait is on whenever `Span` is available and should only be deactivated when `Span` is not available.
|
||||||
|
|
||||||
|
For Swift 6.0 and earlier versions, `SubprocessFoundation` is essentially always enabled, and `SubprocessSpan` is essentially always disabled.
|
||||||
|
|
||||||
|
|
||||||
### The `run()` Family of Methods
|
### The `run()` Family of Methods
|
||||||
@ -218,8 +228,8 @@ Within this package, we propose two modules: `Subprocess` and `SubprocessFoundat
|
|||||||
We propose several `run()` functions that allow developers to asynchronously execute a subprocess.
|
We propose several `run()` functions that allow developers to asynchronously execute a subprocess.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
/// Run a executable with given parameters and a custom closure
|
/// Run a executable with given parameters asynchrously and returns
|
||||||
/// to manage the running subprocess' lifetime and its IOs.
|
/// a `CollectedResult` containing the output of the child process.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - executable: The executable to run.
|
/// - executable: The executable to run.
|
||||||
/// - arguments: The arguments to pass to the executable.
|
/// - arguments: The arguments to pass to the executable.
|
||||||
@ -230,9 +240,10 @@ We propose several `run()` functions that allow developers to asynchronously exe
|
|||||||
/// - input: The input to send to the executable.
|
/// - input: The input to send to the executable.
|
||||||
/// - output: The method to use for redirecting the standard output.
|
/// - output: The method to use for redirecting the standard output.
|
||||||
/// - error: The method to use for redirecting the standard error.
|
/// - error: The method to use for redirecting the standard error.
|
||||||
/// - body: The custom execution body to manually control the running process
|
|
||||||
/// - Returns a CollectedResult containing the result of the run.
|
/// - Returns a CollectedResult containing the result of the run.
|
||||||
@available(macOS 9999, *) // Span availability
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public func run<
|
public func run<
|
||||||
Input: InputProtocol,
|
Input: InputProtocol,
|
||||||
Output: OutputProtocol,
|
Output: OutputProtocol,
|
||||||
@ -248,8 +259,8 @@ public func run<
|
|||||||
error: Error = .discarded
|
error: Error = .discarded
|
||||||
) async throws -> CollectedResult<Output, Error>
|
) async throws -> CollectedResult<Output, Error>
|
||||||
|
|
||||||
/// Run a executable with given parameters and a custom closure
|
/// Run a executable with given parameters asynchrously and returns
|
||||||
/// to manage the running subprocess' lifetime and its IOs.
|
/// a `CollectedResult` containing the output of the child process.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - executable: The executable to run.
|
/// - executable: The executable to run.
|
||||||
/// - arguments: The arguments to pass to the executable.
|
/// - arguments: The arguments to pass to the executable.
|
||||||
@ -257,12 +268,12 @@ public func run<
|
|||||||
/// - workingDirectory: The working directory in which to run the executable.
|
/// - workingDirectory: The working directory in which to run the executable.
|
||||||
/// - platformOptions: The platform specific options to use
|
/// - platformOptions: The platform specific options to use
|
||||||
/// when running the executable.
|
/// when running the executable.
|
||||||
/// - input: span to write to subprocess' standard input.
|
/// - input: The input to send to the executable.
|
||||||
/// - output: The method to use for redirecting the standard output.
|
/// - output: The method to use for redirecting the standard output.
|
||||||
/// - error: The method to use for redirecting the standard error.
|
/// - error: The method to use for redirecting the standard error.
|
||||||
/// - body: The custom execution body to manually control the running process
|
|
||||||
/// - Returns a CollectedResult containing the result of the run.
|
/// - Returns a CollectedResult containing the result of the run.
|
||||||
@available(macOS 9999, *) // Span availability
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
public func run<
|
public func run<
|
||||||
InputElement: BitwiseCopyable,
|
InputElement: BitwiseCopyable,
|
||||||
Output: OutputProtocol,
|
Output: OutputProtocol,
|
||||||
@ -277,6 +288,7 @@ public func run<
|
|||||||
output: Output = .string,
|
output: Output = .string,
|
||||||
error: Error = .discarded
|
error: Error = .discarded
|
||||||
) async throws -> CollectedResult<Output, Error>
|
) async throws -> CollectedResult<Output, Error>
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Run a executable with given parameters and a custom closure
|
/// Run a executable with given parameters and a custom closure
|
||||||
/// to manage the running subprocess' lifetime and its IOs.
|
/// to manage the running subprocess' lifetime and its IOs.
|
||||||
@ -293,6 +305,9 @@ public func run<
|
|||||||
/// - body: The custom execution body to manually control the running process
|
/// - body: The custom execution body to manually control the running process
|
||||||
/// - Returns a ExecutableResult type containing the return value
|
/// - Returns a ExecutableResult type containing the return value
|
||||||
/// of the closure.
|
/// of the closure.
|
||||||
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public func run<Result, Input: InputProtocol, Output: OutputProtocol, Error: OutputProtocol>(
|
public func run<Result, Input: InputProtocol, Output: OutputProtocol, Error: OutputProtocol>(
|
||||||
_ executable: Executable,
|
_ executable: Executable,
|
||||||
arguments: Arguments = [],
|
arguments: Arguments = [],
|
||||||
@ -303,7 +318,7 @@ public func run<Result, Input: InputProtocol, Output: OutputProtocol, Error: Out
|
|||||||
output: Output,
|
output: Output,
|
||||||
error: Error,
|
error: Error,
|
||||||
isolation: isolated (any Actor)? = #isolation,
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
body: (@escaping (Execution<Output, Error>) async throws -> Result)
|
body: ((Execution<Output, Error>) async throws -> Result)
|
||||||
) async throws -> ExecutionResult<Result> where Output.OutputType == Void, Error.OutputType == Void
|
) async throws -> ExecutionResult<Result> where Output.OutputType == Void, Error.OutputType == Void
|
||||||
|
|
||||||
|
|
||||||
@ -322,6 +337,9 @@ public func run<Result, Input: InputProtocol, Output: OutputProtocol, Error: Out
|
|||||||
/// - body: The custom execution body to manually control the running process
|
/// - body: The custom execution body to manually control the running process
|
||||||
/// - Returns a ExecutableResult type containing the return value
|
/// - Returns a ExecutableResult type containing the return value
|
||||||
/// of the closure.
|
/// of the closure.
|
||||||
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public func run<Result, Output: OutputProtocol, Error: OutputProtocol>(
|
public func run<Result, Output: OutputProtocol, Error: OutputProtocol>(
|
||||||
_ executable: Executable,
|
_ executable: Executable,
|
||||||
arguments: Arguments = [],
|
arguments: Arguments = [],
|
||||||
@ -331,18 +349,20 @@ public func run<Result, Output: OutputProtocol, Error: OutputProtocol>(
|
|||||||
output: Output,
|
output: Output,
|
||||||
error: Error,
|
error: Error,
|
||||||
isolation: isolated (any Actor)? = #isolation,
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
body: (@escaping (Execution<Output, Error>, StandardInputWriter) async throws -> Result)
|
body: ((Execution<Output, Error>, StandardInputWriter) async throws -> Result)
|
||||||
) async throws -> ExecutionResult<Result> where Output.OutputType == Void, Error.OutputType == Void
|
) async throws -> ExecutionResult<Result> where Output.OutputType == Void, Error.OutputType == Void
|
||||||
|
|
||||||
/// Run a executable with given parameters and a custom closure
|
/// Run a `Configuration` asynchrously and returns
|
||||||
/// to manage the running subprocess' lifetime and its IOs.
|
/// a `CollectedResult` containing the output of the child process.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - configuration: The `Subprocess` configuration to run.
|
/// - configuration: The `Subprocess` configuration to run.
|
||||||
/// - input: The input to send to the executable.
|
/// - input: The input to send to the executable.
|
||||||
/// - output: The method to use for redirecting the standard output.
|
/// - output: The method to use for redirecting the standard output.
|
||||||
/// - error: The method to use for redirecting the standard error.
|
/// - error: The method to use for redirecting the standard error.
|
||||||
/// - Returns a CollectedResult containing the result of the run.
|
/// - Returns a CollectedResult containing the result of the run.
|
||||||
@available(macOS 9999, *)
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public func run<
|
public func run<
|
||||||
Input: InputProtocol,
|
Input: InputProtocol,
|
||||||
Output: OutputProtocol,
|
Output: OutputProtocol,
|
||||||
@ -362,10 +382,13 @@ public func run<
|
|||||||
/// the running process and write to its standard input.
|
/// the running process and write to its standard input.
|
||||||
/// - Returns a ExecutableResult type containing the return value
|
/// - Returns a ExecutableResult type containing the return value
|
||||||
/// of the closure.
|
/// of the closure.
|
||||||
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public func run<Result>(
|
public func run<Result>(
|
||||||
_ configuration: Configuration,
|
_ configuration: Configuration,
|
||||||
isolation: isolated (any Actor)? = #isolation,
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
body: (@escaping (Execution<SequenceOutput, DiscardedOutput>, StandardInputWriter) async throws -> Result)
|
body: ((Execution<SequenceOutput, DiscardedOutput>, StandardInputWriter) async throws -> Result)
|
||||||
) async throws -> ExecutionResult<Result>
|
) async throws -> ExecutionResult<Result>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -520,8 +543,6 @@ extension Execution where Error == SequenceOutput {
|
|||||||
public struct ProcessIdentifier: Sendable, Hashable, Codable {
|
public struct ProcessIdentifier: Sendable, Hashable, Codable {
|
||||||
/// Windows specifc process identifier value
|
/// Windows specifc process identifier value
|
||||||
public let value: DWORD
|
public let value: DWORD
|
||||||
/// Windows specific thread identifier associated with process
|
|
||||||
public let threadID: DWORD
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
/// A platform independent identifier for a Subprocess.
|
/// A platform independent identifier for a Subprocess.
|
||||||
@ -611,23 +632,36 @@ extension Execution {
|
|||||||
#endif // canImport(Glibc) || canImport(Darwin)
|
#endif // canImport(Glibc) || canImport(Darwin)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Teardown Sequence (macOS and Linux)
|
#### Teardown Sequence
|
||||||
|
|
||||||
`Subprocess` provides a graceful shutdown mechanism for child processes using the `.teardown(using:)` method. This method allows for a sequence of teardown steps to be executed, with the final step always sending a `.kill` signal.
|
`Subprocess` provides a graceful shutdown mechanism for child processes using the `.teardown(using:)` method. This method allows for a sequence of teardown steps to be executed, with the final step always sending a `.kill` signal on Unix or forcefully terminating the process on Windows.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
#if canImport(Glibc) || canImport(Darwin)
|
|
||||||
/// A step in the graceful shutdown teardown sequence.
|
/// A step in the graceful shutdown teardown sequence.
|
||||||
/// It consists of a signal to send to the child process and the
|
/// It consists of an action to perform on the child process and the
|
||||||
/// duration allowed for the child process to exit before proceeding
|
/// duration allowed for the child process to exit before proceeding
|
||||||
/// to the next step.
|
/// to the next step.
|
||||||
public struct TeardownStep: Sendable, Hashable {
|
public struct TeardownStep: Sendable, Hashable {
|
||||||
/// Sends `signal` to the process and allows `allowedDurationToExit`
|
#if !os(Windows)
|
||||||
|
/// Sends `signal` to the process and allows `allowedDurationToNextStep`
|
||||||
/// for the process to exit before proceeding to the next step.
|
/// for the process to exit before proceeding to the next step.
|
||||||
/// The final step in the sequence will always send a `.kill` signal.
|
/// The final step in the sequence will always send a `.kill` signal.
|
||||||
public static func sendSignal(
|
public static func sendSignal(
|
||||||
_ signal: Signal,
|
_ signal: Signal,
|
||||||
allowedDurationToExit: Duration
|
allowedDurationToNextStep: Duration
|
||||||
|
) -> Self
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Attempt to perform a graceful shutdown and allows
|
||||||
|
/// `alloweDurationToNextStep` for the process to exit
|
||||||
|
/// before proceeding to the next step:
|
||||||
|
/// - On Unix: send `SIGTERM`
|
||||||
|
/// - On Windows:
|
||||||
|
/// 1. Attempt to send `VM_CLOSE` if the child process is a GUI process;
|
||||||
|
/// 2. Attempt to send `CTRL_C_EVENT` to console;
|
||||||
|
/// 3. Attempt to send `CTRL_BREAK_EVENT` to process group.
|
||||||
|
public static func gracefulShutDown(
|
||||||
|
alloweDurationToNextStep: Duration
|
||||||
) -> Self
|
) -> Self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,12 +669,11 @@ extension Execution {
|
|||||||
/// Performs a sequence of teardown steps on the Subprocess.
|
/// Performs a sequence of teardown steps on the Subprocess.
|
||||||
/// Teardown sequence always ends with a `.kill` signal
|
/// Teardown sequence always ends with a `.kill` signal
|
||||||
/// - Parameter sequence: The steps to perform.
|
/// - Parameter sequence: The steps to perform.
|
||||||
public func teardown(using sequence: [TeardownStep]) async
|
public func teardown(using sequence: some Sequence<TeardownStep> & Sendable) async
|
||||||
}
|
}
|
||||||
#endif // canImport(Glibc) || canImport(Darwin)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A teardown sequence is a series of signals sent to the child process, accompanied by a specified time limit for the child process to terminate before proceeding to the next step. For instance, it may be appropriate to initially send `.quit` and `.terminate` signals to the child process to facilitate a graceful shutdown before sending `.kill`.
|
A teardown sequence involves a set of actions taken on the child process, with a set time limit for it to wrap up before moving on. On platforms like Darwin and Linux, developers can also send signals directly to the child process. For example, it might be wise to start with `.quit` and `.terminate` signals to ensure a smooth shutdown before resorting to `.kill`.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let result = try await run(
|
let result = try await run(
|
||||||
@ -649,8 +682,8 @@ let result = try await run(
|
|||||||
) { execution in
|
) { execution in
|
||||||
// ... more work
|
// ... more work
|
||||||
await execution.teardown(using: [
|
await execution.teardown(using: [
|
||||||
.sendSignal(.quit, allowedDurationToExit: .milliseconds(100)),
|
.sendSignal(.quit, allowedDurationToNextStep: .milliseconds(100)),
|
||||||
.sendSignal(.terminate, allowedDurationToExit: .milliseconds(100)),
|
.sendSignal(.terminate, allowedDurationToNextStep: .milliseconds(100)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -728,6 +761,16 @@ public final actor StandardInputWriter {
|
|||||||
_ array: [UInt8]
|
_ array: [UInt8]
|
||||||
) async throws -> Int
|
) async throws -> Int
|
||||||
|
|
||||||
|
/// Write a `RawSpan` to the standard input of the subprocess.
|
||||||
|
/// - Parameter span: The span to write
|
||||||
|
/// - Returns number of bytes written
|
||||||
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
public func write(
|
||||||
|
_ span: borrowing RawSpan
|
||||||
|
) async throws -> Int
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Write a StringProtocol to the standard input of the subprocess.
|
/// Write a StringProtocol to the standard input of the subprocess.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - string: The string to write.
|
/// - string: The string to write.
|
||||||
@ -743,9 +786,10 @@ public final actor StandardInputWriter {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`SubprocessFoundation` extends `StandardInputWriter` to work with `Data`:
|
`SubprocessFoundation` trait extends `StandardInputWriter` to work with `Data`:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
#if SubprocessFoundation
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension StandardInputWriter {
|
extension StandardInputWriter {
|
||||||
@ -763,6 +807,7 @@ extension StandardInputWriter {
|
|||||||
_ asyncSequence: AsyncSendableSequence
|
_ asyncSequence: AsyncSendableSequence
|
||||||
) async throws -> Int where AsyncSendableSequence.Element == Data
|
) async throws -> Int where AsyncSendableSequence.Element == Data
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -988,6 +1033,11 @@ public struct PlatformOptions: Sendable, Hashable {
|
|||||||
/// The process identifier of the new process group
|
/// The process identifier of the new process group
|
||||||
/// is the same as the process identifier.
|
/// is the same as the process identifier.
|
||||||
public var createProcessGroup: Bool = false
|
public var createProcessGroup: Bool = false
|
||||||
|
/// An ordered list of steps in order to tear down the child
|
||||||
|
/// process in case the parent task is cancelled before
|
||||||
|
/// the child proces terminates.
|
||||||
|
/// Always ends in forcefully terminate at the end.
|
||||||
|
public var teardownSequence: [TeardownStep] = []
|
||||||
/// A closure to configure platform-specific
|
/// A closure to configure platform-specific
|
||||||
/// spawning constructs. This closure enables direct
|
/// spawning constructs. This closure enables direct
|
||||||
/// configuration or override of underlying platform-specific
|
/// configuration or override of underlying platform-specific
|
||||||
@ -1035,7 +1085,7 @@ _(For more information on these values, checkout Microsoft's documentation [here
|
|||||||
|
|
||||||
### `InputProtocol`
|
### `InputProtocol`
|
||||||
|
|
||||||
`InputProtocol` defines a set of methods that a type must implement to serve as the input source for a subprocess. In most cases, developers should utilize the concrete input types provided by `Subprocess`. However, developers have the option to create their own input types by conforming to `ManagedInputProtocol` and implementing the `write(into:)` method.
|
`InputProtocol` defines the `write(with:)` method that a type must implement to serve as the input source for a subprocess. In most cases, developers should utilize the concrete input types provided by `Subprocess`.
|
||||||
|
|
||||||
The core `Subprocess` module is distributed with the following concrete input types:
|
The core `Subprocess` module is distributed with the following concrete input types:
|
||||||
|
|
||||||
@ -1047,36 +1097,12 @@ The core `Subprocess` module is distributed with the following concrete input ty
|
|||||||
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
/// `InputProtocol` specifies the set of methods that a type
|
/// `InputProtocol` defines the `write(with:)` method that a type
|
||||||
/// must implement to serve as the input source for a subprocess.
|
/// must implement to serve as the input source for a subprocess.
|
||||||
/// Instead of developing custom implementations of `InputProtocol`,
|
|
||||||
/// it is recommended to utilize the default implementations provided
|
|
||||||
/// by the `Subprocess` library to specify the input handling requirements.
|
|
||||||
public protocol InputProtocol: Sendable {
|
public protocol InputProtocol: Sendable {
|
||||||
/// Lazily create and return the FileDescriptor for reading
|
|
||||||
func readFileDescriptor() throws -> FileDescriptor?
|
|
||||||
/// Lazily create and return the FileDescriptor for writing
|
|
||||||
func writeFileDescriptor() throws -> FileDescriptor?
|
|
||||||
|
|
||||||
/// Close the FileDescriptor for reading
|
|
||||||
func closeReadFileDescriptor() throws
|
|
||||||
/// Close the FileDescriptor for writing
|
|
||||||
func closeWriteFileDescriptor() throws
|
|
||||||
|
|
||||||
/// Asynchronously write the input to the subprocess using the
|
/// Asynchronously write the input to the subprocess using the
|
||||||
/// write file descriptor
|
/// write file descriptor
|
||||||
func write(into writeFileDescriptor: FileDescriptor) async throws
|
func write(with writer: StandardInputWriter) async throws
|
||||||
}
|
|
||||||
|
|
||||||
/// `ManagedInputProtocol` is managed by `Subprocess` and
|
|
||||||
/// utilizes its `Pipe` type to facilitate input writing.
|
|
||||||
/// Developers have the option to implement custom
|
|
||||||
/// input types by conforming to `ManagedInputProtocol`
|
|
||||||
/// and implementing the `write(into:)` method.
|
|
||||||
public protocol ManagedInputProtocol: InputProtocol {
|
|
||||||
/// The underlying pipe used by this input in order to
|
|
||||||
/// write input to child process
|
|
||||||
var pipe: Pipe { get }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that indicates
|
/// A concrete `Input` type for subprocesses that indicates
|
||||||
@ -1084,31 +1110,31 @@ public protocol ManagedInputProtocol: InputProtocol {
|
|||||||
/// `NoInput` redirects the standard input of the subprocess
|
/// `NoInput` redirects the standard input of the subprocess
|
||||||
/// to `/dev/null`, while on Windows, it does not bind any
|
/// to `/dev/null`, while on Windows, it does not bind any
|
||||||
/// file handle to the subprocess standard input handle.
|
/// file handle to the subprocess standard input handle.
|
||||||
public final class NoInput: InputProtocol { }
|
public struct NoInput: InputProtocol { }
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that
|
/// A concrete `Input` type for subprocesses that
|
||||||
/// reads input from a specified `FileDescriptor`.
|
/// reads input from a specified `FileDescriptor`.
|
||||||
/// Developers have the option to instruct the `Subprocess` to
|
/// Developers have the option to instruct the `Subprocess` to
|
||||||
/// automatically close the provided `FileDescriptor`
|
/// automatically close the provided `FileDescriptor`
|
||||||
/// after the subprocess is spawned.
|
/// after the subprocess is spawned.
|
||||||
public final class FileDescriptorInput: InputProtocol { }
|
public struct FileDescriptorInput: InputProtocol { }
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that reads input
|
/// A concrete `Input` type for subprocesses that reads input
|
||||||
/// from a given type conforming to `StringProtocol`.
|
/// from a given type conforming to `StringProtocol`.
|
||||||
/// Developers can specify the string encoding to use when
|
/// Developers can specify the string encoding to use when
|
||||||
/// encoding the string to data, which defaults to UTF-8.
|
/// encoding the string to data, which defaults to UTF-8.
|
||||||
public final class StringInput<
|
public struct StringInput<
|
||||||
InputString: StringProtocol & Sendable,
|
InputString: StringProtocol & Sendable,
|
||||||
Encoding: Unicode.Encoding
|
Encoding: Unicode.Encoding
|
||||||
>: ManagedInputProtocol { }
|
>: InputProtocol { }
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that reads input
|
/// A concrete `Input` type for subprocesses that reads input
|
||||||
/// from a given `UInt8` Array.
|
/// from a given `UInt8` Array.
|
||||||
public final class ArrayInput: ManagedInputProtocol { }
|
public struct ArrayInput: InputProtocol { }
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocess that indicates that
|
/// A concrete `Input` type for subprocess that indicates that
|
||||||
/// the Subprocess should read its input from `StandardInputWriter`.
|
/// the Subprocess should read its input from `StandardInputWriter`.
|
||||||
public struct CustomWriteInput: ManagedInputProtocol { }
|
public struct CustomWriteInput: InputProtocol { }
|
||||||
|
|
||||||
extension InputProtocol where Self == NoInput {
|
extension InputProtocol where Self == NoInput {
|
||||||
/// Create a Subprocess input that specfies there is no input
|
/// Create a Subprocess input that specfies there is no input
|
||||||
@ -1149,18 +1175,19 @@ extension InputProtocol {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`SubprocessFoundation` adds the following concrete input types that work with `Data`:
|
`SubprocessFoundation` trait adds the following concrete input types that work with `Data`:
|
||||||
|
|
||||||
- `DataInput`: reads input from a given `Data`.
|
- `DataInput`: reads input from a given `Data`.
|
||||||
- `DataSequenceInput`: reads input from a given sequence of `Data`.
|
- `DataSequenceInput`: reads input from a given sequence of `Data`.
|
||||||
- `DataAsyncSequenceInput`: reads input from a given async sequence of `Data`.
|
- `DataAsyncSequenceInput`: reads input from a given async sequence of `Data`.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
#if SubprocessFoundation
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that reads input
|
/// A concrete `Input` type for subprocesses that reads input
|
||||||
/// from a given `Data`.
|
/// from a given `Data`.
|
||||||
public final class DataInput: ManagedInputProtocol { }
|
public struct DataInput: ManagedInputProtocol { }
|
||||||
|
|
||||||
/// A concrete `Input` type for subprocesses that accepts input
|
/// A concrete `Input` type for subprocesses that accepts input
|
||||||
/// from a specified sequence of `Data`. This type should be preferred
|
/// from a specified sequence of `Data`. This type should be preferred
|
||||||
@ -1191,6 +1218,7 @@ extension InputProtocol {
|
|||||||
_ asyncSequence: InputSequence
|
_ asyncSequence: InputSequence
|
||||||
) -> Self where Self == DataAsyncSequenceInput<InputSequence>
|
) -> Self where Self == DataAsyncSequenceInput<InputSequence>
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
```
|
```
|
||||||
|
|
||||||
Here are some examples:
|
Here are some examples:
|
||||||
@ -1218,7 +1246,9 @@ let exe = try await run(.path("/some/executable"), input: .sequence(sequence))
|
|||||||
### `OutputProtocol`
|
### `OutputProtocol`
|
||||||
|
|
||||||
|
|
||||||
`OutputProtocol` defines the set of methods that a type must implement to serve as the output target for a subprocess. Similarly to `InputProtocol`, developers should utilize the built-in concrete `Output` types provided with `Subprocess` whenever feasible; alternatively, they can create a type that conforms to `ManagedOutputProtocol` and implements `func output(from:) throws -> OutputType` for custom outputs.
|
`OutputProtocol` defines the set of methods that a type must implement to serve as the output target for a subprocess. Similarly to `InputProtocol`, developers should utilize the built-in concrete `Output` types provided with `Subprocess` whenever feasible.
|
||||||
|
|
||||||
|
`OutputProtocol` was primarily designed with `RawSpan` as the primary "currency type". When `RawSpan` is not available, or when the `SubprocessSpan` trait is off, `OutputProtocol` falls back to `Sequence<UInt8>`.
|
||||||
|
|
||||||
The core `Subprocess` module comes with the following concrete output types:
|
The core `Subprocess` module comes with the following concrete output types:
|
||||||
|
|
||||||
@ -1235,43 +1265,36 @@ The core `Subprocess` module comes with the following concrete output types:
|
|||||||
/// Instead of developing custom implementations of `OutputProtocol`,
|
/// Instead of developing custom implementations of `OutputProtocol`,
|
||||||
/// it is recommended to utilize the default implementations provided
|
/// it is recommended to utilize the default implementations provided
|
||||||
/// by the `Subprocess` library to specify the output handling requirements.
|
/// by the `Subprocess` library to specify the output handling requirements.
|
||||||
|
#if SubprocessSpan
|
||||||
|
@available(SubprocessSpan, *)
|
||||||
|
#endif
|
||||||
public protocol OutputProtocol: Sendable {
|
public protocol OutputProtocol: Sendable {
|
||||||
associatedtype OutputType: Sendable
|
associatedtype OutputType: Sendable
|
||||||
/// Lazily create and return the FileDescriptor for reading
|
|
||||||
func readFileDescriptor() throws -> FileDescriptor?
|
|
||||||
/// Lazily create and return the FileDescriptor for writing
|
|
||||||
func writeFileDescriptor() throws -> FileDescriptor?
|
|
||||||
/// Return the read `FileDescriptor` and remove it from the output
|
|
||||||
/// such that the next call to `consumeReadFileDescriptor` will
|
|
||||||
/// return `nil`.
|
|
||||||
func consumeReadFileDescriptor() -> FileDescriptor?
|
|
||||||
|
|
||||||
/// Close the FileDescriptor for reading
|
#if SubprocessSpan
|
||||||
func closeReadFileDescriptor() throws
|
/// Convert the output from span to expected output type
|
||||||
/// Close the FileDescriptor for writing
|
|
||||||
func closeWriteFileDescriptor() throws
|
|
||||||
|
|
||||||
/// Capture the output from the subprocess
|
|
||||||
func captureOutput() async throws -> OutputType
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ManagedOutputProtocol` is managed by `Subprocess` and
|
|
||||||
/// utilizes its `Pipe` type to facilitate output reading.
|
|
||||||
/// Developers have the option to implement custom input types
|
|
||||||
/// by conforming to `ManagedOutputProtocol`
|
|
||||||
/// and implementing the `output(from:)` method.
|
|
||||||
@available(macOS 9999, *) // Span equivelent
|
|
||||||
public protocol ManagedOutputProtocol: OutputProtocol {
|
|
||||||
/// The underlying pipe used by this output in order to
|
|
||||||
/// read from the child process
|
|
||||||
var pipe: Pipe { get }
|
|
||||||
|
|
||||||
/// Convert the output from Data to expected output type
|
|
||||||
func output(from span: RawSpan) throws -> OutputType
|
func output(from span: RawSpan) throws -> OutputType
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Convert the output from buffer to expected output type
|
||||||
|
func output(from buffer: some Sequence<UInt8>) throws -> OutputType
|
||||||
|
|
||||||
/// The max amount of data to collect for this output.
|
/// The max amount of data to collect for this output.
|
||||||
var maxSize: Int { get }
|
var maxSize: Int { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension OutputProtocol {
|
||||||
|
/// Default implementation provided
|
||||||
|
public var maxSize: Int { 128 * 1024 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SubprocessSpan
|
||||||
|
extension OutputProtocol {
|
||||||
|
// Default implementation provided
|
||||||
|
public func output(from buffer: some Sequence<UInt8>) throws -> OutputType { ... }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// A concrete `Output` type for subprocesses that indicates that
|
/// A concrete `Output` type for subprocesses that indicates that
|
||||||
/// the `Subprocess` should not collect or redirect output
|
/// the `Subprocess` should not collect or redirect output
|
||||||
/// from the child process. On Unix-like systems, `DiscardedOutput`
|
/// from the child process. On Unix-like systems, `DiscardedOutput`
|
||||||
@ -1291,14 +1314,12 @@ public struct FileDescriptorOutput: OutputProtocol { }
|
|||||||
/// from the subprocess as `String` with the given encoding.
|
/// from the subprocess as `String` with the given encoding.
|
||||||
/// This option must be used with he `run()` method that
|
/// This option must be used with he `run()` method that
|
||||||
/// returns a `CollectedResult`.
|
/// returns a `CollectedResult`.
|
||||||
@available(macOS 9999, *) // Span equivelent
|
public struct StringOutput<Encoding: Unicode.Encoding>: OutputProtocol { }
|
||||||
public struct StringOutput<Encoding: Unicode.Encoding>: ManagedOutputProtocol { }
|
|
||||||
|
|
||||||
/// A concrete `Output` type for subprocesses that collects output
|
/// A concrete `Output` type for subprocesses that collects output
|
||||||
/// from the subprocess as `[UInt8]`. This option must be used with
|
/// from the subprocess as `[UInt8]`. This option must be used with
|
||||||
/// the `run()` method that returns a `CollectedResult`
|
/// the `run()` method that returns a `CollectedResult`
|
||||||
@available(macOS 9999, *)
|
public struct BytesOutput: OutputProtocol { }
|
||||||
public final class BytesOutput: ManagedOutputProtocol { }
|
|
||||||
|
|
||||||
/// A concrete `Output` type for subprocesses that redirects
|
/// A concrete `Output` type for subprocesses that redirects
|
||||||
/// the child output to the `.standardOutput` or `.standardError`
|
/// the child output to the `.standardOutput` or `.standardError`
|
||||||
@ -1328,12 +1349,13 @@ extension OutputProtocol where Self == SequenceOutput {
|
|||||||
public static var sequence: Self { .init() }
|
public static var sequence: Self { .init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 9999, *) // Span equivelent
|
extension OutputProtocol where Self == StringOutput<UTF8> {
|
||||||
extension OutputProtocol {
|
|
||||||
/// Create a `Subprocess` output that collects output as
|
/// Create a `Subprocess` output that collects output as
|
||||||
/// UTF8 String with 128kb limit.
|
/// UTF8 String with 128kb limit.
|
||||||
public static func string() -> Self where Self == StringOutput<UTF8>
|
public static var string: Self
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OutputProtocol {
|
||||||
/// Create a `Subprocess` output that collects output as
|
/// Create a `Subprocess` output that collects output as
|
||||||
/// `String` using the given encoding up to limit it bytes.
|
/// `String` using the given encoding up to limit it bytes.
|
||||||
public static func string<Encoding: Unicode.Encoding>(
|
public static func string<Encoding: Unicode.Encoding>(
|
||||||
@ -1342,7 +1364,6 @@ extension OutputProtocol {
|
|||||||
) -> Self where Self == StringOutput<Encoding>
|
) -> Self where Self == StringOutput<Encoding>
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(macOS 9999, *) // Span equivelent
|
|
||||||
extension OutputProtocol where Self == BytesOutput {
|
extension OutputProtocol where Self == BytesOutput {
|
||||||
/// Create a `Subprocess` output that collects output as
|
/// Create a `Subprocess` output that collects output as
|
||||||
/// `Buffer` with 128kb limit.
|
/// `Buffer` with 128kb limit.
|
||||||
@ -1355,21 +1376,20 @@ extension OutputProtocol where Self == BytesOutput {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
`SubprocessFoundation` adds one additional concrete input:
|
`SubprocessFoundation` trait adds one additional concrete input:
|
||||||
|
|
||||||
- `DataOutput`: collects output from the subprocess as `Data`.
|
- `DataOutput`: collects output from the subprocess as `Data`.
|
||||||
|
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
#if SubprocessFoundation
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A concrete `Output` type for subprocesses that collects output
|
/// A concrete `Output` type for subprocesses that collects output
|
||||||
/// from the subprocess as `Data`. This option must be used with
|
/// from the subprocess as `Data`. This option must be used with
|
||||||
/// the `run()` method that returns a `CollectedResult`
|
/// the `run()` method that returns a `CollectedResult`
|
||||||
@available(macOS 9999, *) // Span equivelent
|
public struct DataOutput: OutputProtocol { }
|
||||||
public struct DataOutput: ManagedOutputProtocol { }
|
|
||||||
|
|
||||||
@available(macOS 9999, *) // Span equivelent
|
|
||||||
extension OutputProtocol where Self == DataOutput {
|
extension OutputProtocol where Self == DataOutput {
|
||||||
/// Create a `Subprocess` output that collects output as `Data`
|
/// Create a `Subprocess` output that collects output as `Data`
|
||||||
/// up to 128kb.
|
/// up to 128kb.
|
||||||
@ -1383,6 +1403,7 @@ extension OutputProtocol where Self == DataOutput {
|
|||||||
return .init(limit: limit)
|
return .init(limit: limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
```
|
```
|
||||||
|
|
||||||
Here are some examples of using different outputs:
|
Here are some examples of using different outputs:
|
||||||
@ -1434,9 +1455,11 @@ extension SequenceOutput {
|
|||||||
_ body: (UnsafeRawBufferPointer) throws -> ResultType
|
_ body: (UnsafeRawBufferPointer) throws -> ResultType
|
||||||
) rethrows -> ResultType
|
) rethrows -> ResultType
|
||||||
|
|
||||||
|
#if SubprocessSpan
|
||||||
/// Access the bytes stored in this buffer as `RawSpan`
|
/// Access the bytes stored in this buffer as `RawSpan`
|
||||||
@available(macOS 9999, *) // Span equivalent
|
@available(SubprocessSpan, *)
|
||||||
var bytes: RawSpan { get }
|
var bytes: RawSpan { get }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1456,7 +1479,7 @@ let catResult = try await Subprocess.run(
|
|||||||
if value.contains("Done") {
|
if value.contains("Done") {
|
||||||
await execution.teardown(
|
await execution.teardown(
|
||||||
using: [
|
using: [
|
||||||
.sendSignal(.quit, allowedDurationToExit: .milliseconds(500)),
|
.sendSignal(.quit, allowedDurationToNextStep: .milliseconds(500)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
@ -1721,18 +1744,6 @@ extension SubprocessError {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### `Pipe`
|
|
||||||
|
|
||||||
`Subprocess` provides an opaque `Pipe` type that creates and manages Unix-like pipes (file descriptor pairs for read and write operations) used by `Subprocess` for communication with child processes. The `Pipe` type is designed to be exclusively utilized by `ManagedInputProtocol` and `ManagedOutputProtocol` as an implementation detail.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
/// A managed, opaque UNIX pipe used by `PipeOutputProtocol` and `PipeInputProtocol`
|
|
||||||
public final class Pipe: Sendable {
|
|
||||||
public init() {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Task Cancellation
|
### Task Cancellation
|
||||||
|
|
||||||
If the task running `Subprocess.run` is cancelled while the child process is running, `Subprocess` will attempt to release all the resources it acquired (i.e. file descriptors) and then terminate the child process according to the `TeardownSequence`.
|
If the task running `Subprocess.run` is cancelled while the child process is running, `Subprocess` will attempt to release all the resources it acquired (i.e. file descriptors) and then terminate the child process according to the `TeardownSequence`.
|
||||||
|
@ -960,6 +960,49 @@ enum _FileOperations {
|
|||||||
private static func _copyDirectoryMetadata(srcFD: CInt, srcPath: @autoclosure () -> String, dstFD: CInt, dstPath: @autoclosure () -> String, delegate: some LinkOrCopyDelegate) throws {
|
private static func _copyDirectoryMetadata(srcFD: CInt, srcPath: @autoclosure () -> String, dstFD: CInt, dstPath: @autoclosure () -> String, delegate: some LinkOrCopyDelegate) throws {
|
||||||
#if !os(WASI) && !os(Android)
|
#if !os(WASI) && !os(Android)
|
||||||
// Copy extended attributes
|
// Copy extended attributes
|
||||||
|
#if os(FreeBSD)
|
||||||
|
// FreeBSD uses the `extattr_*` calls for setting extended attributes. Unlike like, the namespace for the extattrs are not determined by prefix of the attribute
|
||||||
|
for namespace in [EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER] {
|
||||||
|
// if we don't have permission to list attributes in system namespace, this returns -1 and skips it
|
||||||
|
var size = extattr_list_fd(srcFD, namespace, nil, 0)
|
||||||
|
if size > 0 {
|
||||||
|
// we are allocating size + 1 bytes here such that we have room for the last null terminator
|
||||||
|
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size + 1) { keyList in
|
||||||
|
// The list of entry returns by `extattr_list_*` contains the length(1 byte) of the attribute name, follow by the Non-NULL terminated attribute name. (See exattr(2))
|
||||||
|
size = extattr_list_fd(srcFD, namespace, keyList.baseAddress!, size)
|
||||||
|
|
||||||
|
guard size > 0 else { continue }
|
||||||
|
|
||||||
|
var keyLength = Int(keyList.baseAddress!.pointee)
|
||||||
|
var current = keyList.baseAddress!.advanced(by: 1)
|
||||||
|
let end = keyList.baseAddress!.advanced(by: size)
|
||||||
|
keyList.baseAddress!.advanced(by: size).pointee = 0
|
||||||
|
|
||||||
|
while current < end {
|
||||||
|
let nextEntry = current.advanced(by: keyLength)
|
||||||
|
// get the length of next key, if this is the last entry, this points to the explicitly zerod byte at `end`.
|
||||||
|
keyLength = Int(nextEntry.pointee)
|
||||||
|
// zero the length field of the next name, so current name can pass in as a null-terminated string
|
||||||
|
nextEntry.pointee = 0
|
||||||
|
// this also set `current` to `end` after iterating all entries
|
||||||
|
defer { current = nextEntry.advanced(by: 1) }
|
||||||
|
|
||||||
|
var valueSize = extattr_get_fd(srcFD, namespace, current, nil, 0)
|
||||||
|
if valueSize >= 0 {
|
||||||
|
try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valueSize) { valueBuffer in
|
||||||
|
valueSize = extattr_get_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize)
|
||||||
|
if valueSize >= 0 {
|
||||||
|
if extattr_set_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize) != 0 {
|
||||||
|
try delegate.throwIfNecessary(errno, srcPath(), dstPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
var size = flistxattr(srcFD, nil, 0)
|
var size = flistxattr(srcFD, nil, 0)
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size) { keyList in
|
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size) { keyList in
|
||||||
@ -985,6 +1028,7 @@ enum _FileOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
var statInfo = stat()
|
var statInfo = stat()
|
||||||
if fstat(srcFD, &statInfo) == 0 {
|
if fstat(srcFD, &statInfo) == 0 {
|
||||||
#if !os(WASI) // WASI doesn't have fchown for now
|
#if !os(WASI) // WASI doesn't have fchown for now
|
||||||
|
Loading…
x
Reference in New Issue
Block a user