Skip to content

Commit

Permalink
Proposal v4 changes
Browse files Browse the repository at this point in the history
  • Loading branch information
iCharlesHu committed Jul 15, 2024
1 parent bf9aacd commit f001ac6
Show file tree
Hide file tree
Showing 14 changed files with 4,538 additions and 107 deletions.
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ var dependencies: [Package.Dependency] {
exact: "0.0.8"),
.package(
url: "https://github.com/swiftlang/swift-syntax",
from: "600.0.0-latest")
from: "600.0.0-latest"),
.package(
url: "https://github.com/apple/swift-system",
from: "1.0.0")
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import Darwin
import SystemPackage

#if FOUNDATION_FRAMEWORK
@_implementationOnly import _CShims
@_implementationOnly import _FoundationCShims
#else
package import _CShims
internal import _FoundationCShims
#endif

// Darwin specific implementation
Expand Down Expand Up @@ -147,10 +147,10 @@ extension Subprocess.Configuration {
}
}
// Run additional config
if let spawnConfig = self.platformOptions.additionalSpawnAttributeConfigurator {
if let spawnConfig = self.platformOptions.preSpawnAttributeConfigurator {
try spawnConfig(&spawnAttributes)
}
if let fileAttributeConfig = self.platformOptions.additionalFileAttributeConfigurator {
if let fileAttributeConfig = self.platformOptions.preSpawnFileAttributeConfigurator {
try fileAttributeConfig(&fileActions)
}
// Spawn
Expand Down Expand Up @@ -203,8 +203,8 @@ extension Subprocess {
// Create a new process group
public var createProcessGroup: Bool = false
public var launchRequirementData: Data? = nil
public var additionalSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)?
public var additionalFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)?
public var preSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)?
public var preSpawnFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)?

public init(
qualityOfService: QualityOfService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import Glibc
import SystemPackage
import FoundationEssentials
package import _CShims
package import _FoundationCShims

// Linux specific implementations
extension Subprocess.Configuration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import Glibc
import SystemPackage

#if FOUNDATION_FRAMEWORK
@_implementationOnly import _CShims
@_implementationOnly import _FoundationCShims
#else
package import _CShims
package import _FoundationCShims
#endif

import Dispatch
Expand Down Expand Up @@ -283,7 +283,7 @@ internal func monitorProcessTermination(
var status: Int32 = -1
waitpid(pid.value, &status, 0)
if _was_process_exited(status) != 0 {
continuation.resume(returning: .exit(_get_exit_code(status)))
continuation.resume(returning: .exited(_get_exit_code(status)))
return
}
if _was_process_signaled(status) != 0 {
Expand Down
32 changes: 16 additions & 16 deletions Sources/FoundationEssentials/Subprocess/Subprocess+API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ extension Subprocess {
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions = .default,
input: InputMethod = .noInput,
output: RedirectedOutputMethod = .redirect,
error: RedirectedOutputMethod = .discard,
output: RedirectedOutputMethod = .redirectToSequence,
error: RedirectedOutputMethod = .redirectToSequence,
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
return try await Configuration(
executable: executable,
arguments: arguments,
Expand All @@ -150,10 +150,10 @@ extension Subprocess {
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions,
input: some Sequence<UInt8>,
output: RedirectedOutputMethod = .redirect,
error: RedirectedOutputMethod = .discard,
output: RedirectedOutputMethod = .redirectToSequence,
error: RedirectedOutputMethod = .redirectToSequence,
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
return try await Configuration(
executable: executable,
arguments: arguments,
Expand All @@ -175,10 +175,10 @@ extension Subprocess {
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions = .default,
input: S,
output: RedirectedOutputMethod = .redirect,
error: RedirectedOutputMethod = .discard,
output: RedirectedOutputMethod = .redirectToSequence,
error: RedirectedOutputMethod = .redirectToSequence,
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
) async throws -> Result<R> where S.Element == UInt8 {
) async throws -> ExecutionResult<R> where S.Element == UInt8 {
return try await Configuration(
executable: executable,
arguments: arguments,
Expand All @@ -199,10 +199,10 @@ extension Subprocess {
environment: Environment = .inherit,
workingDirectory: FilePath? = nil,
platformOptions: PlatformOptions = .default,
output: RedirectedOutputMethod = .redirect,
error: RedirectedOutputMethod = .discard,
output: RedirectedOutputMethod = .redirectToSequence,
error: RedirectedOutputMethod = .redirectToSequence,
_ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R)
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
return try await Configuration(
executable: executable,
arguments: arguments,
Expand All @@ -217,11 +217,11 @@ extension Subprocess {
// MARK: - Configuration Based
extension Subprocess {
public static func run<R>(
withConfiguration configuration: Configuration,
output: RedirectedOutputMethod = .redirect,
error: RedirectedOutputMethod = .redirect,
using configuration: Configuration,
output: RedirectedOutputMethod = .redirectToSequence,
error: RedirectedOutputMethod = .redirectToSequence,
_ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R)
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
return try await configuration.run(output: output, error: error, body)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import SystemPackage
import Dispatch

extension Subprocess {
public struct AsyncBytes: AsyncSequence, Sendable {
public struct AsyncBytes: AsyncSequence, Sendable, _AsyncSequence {
public typealias Error = any Swift.Error

public typealias Element = UInt8

@_nonSendable
Expand Down Expand Up @@ -104,3 +106,7 @@ extension RangeReplaceableCollection {
}
}
}

public protocol _AsyncSequence<Element, Error>: AsyncSequence {
associatedtype Error
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
@preconcurrency import SystemPackage

#if FOUNDATION_FRAMEWORK
@_implementationOnly import _CShims
@_implementationOnly import _FoundationCShims
#else
package import _CShims
package import _FoundationCShims
#endif

#if canImport(Darwin)
Expand Down Expand Up @@ -182,7 +182,7 @@ extension Subprocess {
output: RedirectedOutputMethod,
error: RedirectedOutputMethod,
_ body: @Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
let (readFd, writeFd) = try FileDescriptor.pipe()
let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd))
let executionOutput: ExecutionOutput = try output.createExecutionOutput()
Expand Down Expand Up @@ -238,7 +238,7 @@ extension Subprocess {
result = workResult
}
}
return Result(terminationStatus: terminationStatus, value: result)
return ExecutionResult(terminationStatus: terminationStatus, value: result)
}
} onCancel: {
// Attempt to terminate the child process
Expand All @@ -258,7 +258,7 @@ extension Subprocess {
output: RedirectedOutputMethod,
error: RedirectedOutputMethod,
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
) async throws -> Result<R> {
) async throws -> ExecutionResult<R> {
let executionInput = try input.createExecutionInput()
let executionOutput = try output.createExecutionOutput()
let executionError = try error.createExecutionOutput()
Expand Down Expand Up @@ -311,7 +311,7 @@ extension Subprocess {
result = workResult
}
}
return Result(terminationStatus: terminationStatus, value: result)
return ExecutionResult(terminationStatus: terminationStatus, value: result)
}
} onCancel: {
// Attempt to terminate the child process
Expand Down Expand Up @@ -484,7 +484,7 @@ extension Subprocess {

// MARK: - TerminationStatus
extension Subprocess {
public enum TerminationStatus: Sendable, Hashable {
public enum TerminationStatus: Sendable, Hashable, Codable {
#if canImport(WinSDK)
public typealias Code = DWORD
#else
Expand All @@ -495,12 +495,12 @@ extension Subprocess {
case stillActive
#endif

case exit(Code)
case exited(Code)
case unhandledException(Code)

public var isSuccess: Bool {
switch self {
case .exit(let exitCode):
case .exited(let exitCode):
return exitCode == 0
case .unhandledException(_):
return false
Expand All @@ -509,7 +509,7 @@ extension Subprocess {

public var isUnhandledException: Bool {
switch self {
case .exit(_):
case .exited(_):
return false
case .unhandledException(_):
return true
Expand Down
14 changes: 7 additions & 7 deletions Sources/FoundationEssentials/Subprocess/Subprocess+IO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ extension Subprocess {
return .init(method: .noInput)
}

public static func readFrom(_ fd: FileDescriptor, closeWhenDone: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeWhenDone))
public static func readFrom(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned))
}
}
}
Expand All @@ -68,8 +68,8 @@ extension Subprocess {
return .init(method: .collected(128 * 1024))
}

public static func writeTo(_ fd: FileDescriptor, closeWhenDone: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeWhenDone))
public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned))
}

public static func collect(limit: Int) -> Self {
Expand Down Expand Up @@ -104,12 +104,12 @@ extension Subprocess {
return .init(method: .discarded)
}

public static var redirect: Self {
public static var redirectToSequence: Self {
return .init(method: .collected(128 * 1024))
}

public static func writeTo(_ fd: FileDescriptor, closeWhenDone: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeWhenDone))
public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self {
return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned))
}

internal func createExecutionOutput() throws -> ExecutionOutput {
Expand Down
22 changes: 17 additions & 5 deletions Sources/FoundationEssentials/Subprocess/Subprocess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ public struct Subprocess: Sendable {
self.executionError = executionError
}

public var standardOutput: AsyncBytes {
/// The standard output of the subprocess.
/// Accessing this property will **fatalError** if
/// - `.output` wasn't set to `.redirectToSequence` when the subprocess was spawned;
/// - This property was accessed multiple times. Subprocess communicates with
/// parent process via pipe under the hood and each pipe can only be consumed ones.
public var standardOutput: some _AsyncSequence<UInt8, any Error> {
guard let (_, fd) = self.executionOutput
.consumeCollectedFileDescriptor() else {
fatalError("The standard output was not redirected")
Expand All @@ -49,7 +54,12 @@ public struct Subprocess: Sendable {
return AsyncBytes(fileDescriptor: fd)
}

public var standardError: AsyncBytes {
/// The standard error of the subprocess.
/// Accessing this property will **fatalError** if
/// - `.error` wasn't set to `.redirectToSequence` when the subprocess was spawned;
/// - This property was accessed multiple times. Subprocess communicates with
/// parent process via pipe under the hood and each pipe can only be consumed ones.
public var standardError: some _AsyncSequence<UInt8, any Error> {
guard let (_, fd) = self.executionError
.consumeCollectedFileDescriptor() else {
fatalError("The standard error was not redirected")
Expand Down Expand Up @@ -101,7 +111,7 @@ extension Subprocess {

// MARK: - Result
extension Subprocess {
public struct Result<T: Sendable>: Sendable {
public struct ExecutionResult<T: Sendable>: Sendable {
public let terminationStatus: TerminationStatus
public let value: T

Expand Down Expand Up @@ -142,9 +152,11 @@ extension Subprocess {
}
}

extension Subprocess.Result: Equatable where T : Equatable {}
extension Subprocess.ExecutionResult: Equatable where T : Equatable {}

extension Subprocess.Result: Hashable where T : Hashable {}
extension Subprocess.ExecutionResult: Hashable where T : Hashable {}

extension Subprocess.ExecutionResult: Codable where T : Codable {}

// MARK: Internal
extension Subprocess {
Expand Down
1 change: 1 addition & 0 deletions Sources/_FoundationCShims/include/_FoundationCShims.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "bplist_shims.h"
#include "io_shims.h"
#include "platform_shims.h"
#include "process_shims.h"
#include "filemanager_shims.h"
#include "uuid.h"

Expand Down
21 changes: 15 additions & 6 deletions Sources/_FoundationCShims/process_shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,22 @@ int _subprocess_fork_exec(
}

// Bind stdin, stdout, and stderr
int rc = dup2(file_descriptors[0], STDIN_FILENO);
if (rc != 0) { return rc; }
rc = dup2(file_descriptors[2], STDOUT_FILENO);
if (rc != 0) { return rc; }
rc = dup2(file_descriptors[4], STDERR_FILENO);
if (rc != 0) { return rc; }
int rc = 0;
if (file_descriptors[0] != 0) {
rc = dup2(file_descriptors[0], STDIN_FILENO);
if (rc != 0) { return rc; }
}
if (file_descriptors[2] != 0) {
rc = dup2(file_descriptors[2], STDOUT_FILENO);
if (rc != 0) { return rc; }
}

if (file_descriptors[4] != 0) {
rc = dup2(file_descriptors[4], STDERR_FILENO);
if (rc != 0) { return rc; }
}

#warning Shold close all and then return error no early return
// Close parent side
if (file_descriptors[1] != 0) {
rc = close(file_descriptors[1]);
Expand Down
Loading

0 comments on commit f001ac6

Please sign in to comment.