Skip to content

Commit f001ac6

Browse files
committed
Proposal v4 changes
1 parent bf9aacd commit f001ac6

File tree

14 files changed

+4538
-107
lines changed

14 files changed

+4538
-107
lines changed

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ var dependencies: [Package.Dependency] {
6464
exact: "0.0.8"),
6565
.package(
6666
url: "https://github.com/swiftlang/swift-syntax",
67-
from: "600.0.0-latest")
67+
from: "600.0.0-latest"),
68+
.package(
69+
url: "https://github.com/apple/swift-system",
70+
from: "1.0.0")
6871
]
6972
}
7073
}

Sources/FoundationEssentials/Subprocess/Platforms/Subprocess+Darwin.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import Darwin
1515
import SystemPackage
1616

1717
#if FOUNDATION_FRAMEWORK
18-
@_implementationOnly import _CShims
18+
@_implementationOnly import _FoundationCShims
1919
#else
20-
package import _CShims
20+
internal import _FoundationCShims
2121
#endif
2222

2323
// Darwin specific implementation
@@ -147,10 +147,10 @@ extension Subprocess.Configuration {
147147
}
148148
}
149149
// Run additional config
150-
if let spawnConfig = self.platformOptions.additionalSpawnAttributeConfigurator {
150+
if let spawnConfig = self.platformOptions.preSpawnAttributeConfigurator {
151151
try spawnConfig(&spawnAttributes)
152152
}
153-
if let fileAttributeConfig = self.platformOptions.additionalFileAttributeConfigurator {
153+
if let fileAttributeConfig = self.platformOptions.preSpawnFileAttributeConfigurator {
154154
try fileAttributeConfig(&fileActions)
155155
}
156156
// Spawn
@@ -203,8 +203,8 @@ extension Subprocess {
203203
// Create a new process group
204204
public var createProcessGroup: Bool = false
205205
public var launchRequirementData: Data? = nil
206-
public var additionalSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)?
207-
public var additionalFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)?
206+
public var preSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)?
207+
public var preSpawnFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)?
208208

209209
public init(
210210
qualityOfService: QualityOfService,

Sources/FoundationEssentials/Subprocess/Platforms/Subprocess+Linux.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import Glibc
1515
import SystemPackage
1616
import FoundationEssentials
17-
package import _CShims
17+
package import _FoundationCShims
1818

1919
// Linux specific implementations
2020
extension Subprocess.Configuration {

Sources/FoundationEssentials/Subprocess/Platforms/Subprocess+Unix.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import Glibc
2121
import SystemPackage
2222

2323
#if FOUNDATION_FRAMEWORK
24-
@_implementationOnly import _CShims
24+
@_implementationOnly import _FoundationCShims
2525
#else
26-
package import _CShims
26+
package import _FoundationCShims
2727
#endif
2828

2929
import Dispatch
@@ -283,7 +283,7 @@ internal func monitorProcessTermination(
283283
var status: Int32 = -1
284284
waitpid(pid.value, &status, 0)
285285
if _was_process_exited(status) != 0 {
286-
continuation.resume(returning: .exit(_get_exit_code(status)))
286+
continuation.resume(returning: .exited(_get_exit_code(status)))
287287
return
288288
}
289289
if _was_process_signaled(status) != 0 {

Sources/FoundationEssentials/Subprocess/Subprocess+API.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,10 @@ extension Subprocess {
129129
workingDirectory: FilePath? = nil,
130130
platformOptions: PlatformOptions = .default,
131131
input: InputMethod = .noInput,
132-
output: RedirectedOutputMethod = .redirect,
133-
error: RedirectedOutputMethod = .discard,
132+
output: RedirectedOutputMethod = .redirectToSequence,
133+
error: RedirectedOutputMethod = .redirectToSequence,
134134
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
135-
) async throws -> Result<R> {
135+
) async throws -> ExecutionResult<R> {
136136
return try await Configuration(
137137
executable: executable,
138138
arguments: arguments,
@@ -150,10 +150,10 @@ extension Subprocess {
150150
workingDirectory: FilePath? = nil,
151151
platformOptions: PlatformOptions,
152152
input: some Sequence<UInt8>,
153-
output: RedirectedOutputMethod = .redirect,
154-
error: RedirectedOutputMethod = .discard,
153+
output: RedirectedOutputMethod = .redirectToSequence,
154+
error: RedirectedOutputMethod = .redirectToSequence,
155155
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
156-
) async throws -> Result<R> {
156+
) async throws -> ExecutionResult<R> {
157157
return try await Configuration(
158158
executable: executable,
159159
arguments: arguments,
@@ -175,10 +175,10 @@ extension Subprocess {
175175
workingDirectory: FilePath? = nil,
176176
platformOptions: PlatformOptions = .default,
177177
input: S,
178-
output: RedirectedOutputMethod = .redirect,
179-
error: RedirectedOutputMethod = .discard,
178+
output: RedirectedOutputMethod = .redirectToSequence,
179+
error: RedirectedOutputMethod = .redirectToSequence,
180180
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
181-
) async throws -> Result<R> where S.Element == UInt8 {
181+
) async throws -> ExecutionResult<R> where S.Element == UInt8 {
182182
return try await Configuration(
183183
executable: executable,
184184
arguments: arguments,
@@ -199,10 +199,10 @@ extension Subprocess {
199199
environment: Environment = .inherit,
200200
workingDirectory: FilePath? = nil,
201201
platformOptions: PlatformOptions = .default,
202-
output: RedirectedOutputMethod = .redirect,
203-
error: RedirectedOutputMethod = .discard,
202+
output: RedirectedOutputMethod = .redirectToSequence,
203+
error: RedirectedOutputMethod = .redirectToSequence,
204204
_ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R)
205-
) async throws -> Result<R> {
205+
) async throws -> ExecutionResult<R> {
206206
return try await Configuration(
207207
executable: executable,
208208
arguments: arguments,
@@ -217,11 +217,11 @@ extension Subprocess {
217217
// MARK: - Configuration Based
218218
extension Subprocess {
219219
public static func run<R>(
220-
withConfiguration configuration: Configuration,
221-
output: RedirectedOutputMethod = .redirect,
222-
error: RedirectedOutputMethod = .redirect,
220+
using configuration: Configuration,
221+
output: RedirectedOutputMethod = .redirectToSequence,
222+
error: RedirectedOutputMethod = .redirectToSequence,
223223
_ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R)
224-
) async throws -> Result<R> {
224+
) async throws -> ExecutionResult<R> {
225225
return try await configuration.run(output: output, error: error, body)
226226
}
227227
}

Sources/FoundationEssentials/Subprocess/Subprocess+AsyncBytes.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import SystemPackage
1313
import Dispatch
1414

1515
extension Subprocess {
16-
public struct AsyncBytes: AsyncSequence, Sendable {
16+
public struct AsyncBytes: AsyncSequence, Sendable, _AsyncSequence {
17+
public typealias Error = any Swift.Error
18+
1719
public typealias Element = UInt8
1820

1921
@_nonSendable
@@ -104,3 +106,7 @@ extension RangeReplaceableCollection {
104106
}
105107
}
106108
}
109+
110+
public protocol _AsyncSequence<Element, Error>: AsyncSequence {
111+
associatedtype Error
112+
}

Sources/FoundationEssentials/Subprocess/Subprocess+Configuration.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
@preconcurrency import SystemPackage
1313

1414
#if FOUNDATION_FRAMEWORK
15-
@_implementationOnly import _CShims
15+
@_implementationOnly import _FoundationCShims
1616
#else
17-
package import _CShims
17+
package import _FoundationCShims
1818
#endif
1919

2020
#if canImport(Darwin)
@@ -182,7 +182,7 @@ extension Subprocess {
182182
output: RedirectedOutputMethod,
183183
error: RedirectedOutputMethod,
184184
_ body: @Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R
185-
) async throws -> Result<R> {
185+
) async throws -> ExecutionResult<R> {
186186
let (readFd, writeFd) = try FileDescriptor.pipe()
187187
let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd))
188188
let executionOutput: ExecutionOutput = try output.createExecutionOutput()
@@ -238,7 +238,7 @@ extension Subprocess {
238238
result = workResult
239239
}
240240
}
241-
return Result(terminationStatus: terminationStatus, value: result)
241+
return ExecutionResult(terminationStatus: terminationStatus, value: result)
242242
}
243243
} onCancel: {
244244
// Attempt to terminate the child process
@@ -258,7 +258,7 @@ extension Subprocess {
258258
output: RedirectedOutputMethod,
259259
error: RedirectedOutputMethod,
260260
_ body: (@Sendable @escaping (Subprocess) async throws -> R)
261-
) async throws -> Result<R> {
261+
) async throws -> ExecutionResult<R> {
262262
let executionInput = try input.createExecutionInput()
263263
let executionOutput = try output.createExecutionOutput()
264264
let executionError = try error.createExecutionOutput()
@@ -311,7 +311,7 @@ extension Subprocess {
311311
result = workResult
312312
}
313313
}
314-
return Result(terminationStatus: terminationStatus, value: result)
314+
return ExecutionResult(terminationStatus: terminationStatus, value: result)
315315
}
316316
} onCancel: {
317317
// Attempt to terminate the child process
@@ -484,7 +484,7 @@ extension Subprocess {
484484

485485
// MARK: - TerminationStatus
486486
extension Subprocess {
487-
public enum TerminationStatus: Sendable, Hashable {
487+
public enum TerminationStatus: Sendable, Hashable, Codable {
488488
#if canImport(WinSDK)
489489
public typealias Code = DWORD
490490
#else
@@ -495,12 +495,12 @@ extension Subprocess {
495495
case stillActive
496496
#endif
497497

498-
case exit(Code)
498+
case exited(Code)
499499
case unhandledException(Code)
500500

501501
public var isSuccess: Bool {
502502
switch self {
503-
case .exit(let exitCode):
503+
case .exited(let exitCode):
504504
return exitCode == 0
505505
case .unhandledException(_):
506506
return false
@@ -509,7 +509,7 @@ extension Subprocess {
509509

510510
public var isUnhandledException: Bool {
511511
switch self {
512-
case .exit(_):
512+
case .exited(_):
513513
return false
514514
case .unhandledException(_):
515515
return true

Sources/FoundationEssentials/Subprocess/Subprocess+IO.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ extension Subprocess {
4040
return .init(method: .noInput)
4141
}
4242

43-
public static func readFrom(_ fd: FileDescriptor, closeWhenDone: Bool) -> Self {
44-
return .init(method: .fileDescriptor(fd, closeWhenDone))
43+
public static func readFrom(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self {
44+
return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned))
4545
}
4646
}
4747
}
@@ -68,8 +68,8 @@ extension Subprocess {
6868
return .init(method: .collected(128 * 1024))
6969
}
7070

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

7575
public static func collect(limit: Int) -> Self {
@@ -104,12 +104,12 @@ extension Subprocess {
104104
return .init(method: .discarded)
105105
}
106106

107-
public static var redirect: Self {
107+
public static var redirectToSequence: Self {
108108
return .init(method: .collected(128 * 1024))
109109
}
110110

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

115115
internal func createExecutionOutput() throws -> ExecutionOutput {

Sources/FoundationEssentials/Subprocess/Subprocess.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ public struct Subprocess: Sendable {
3838
self.executionError = executionError
3939
}
4040

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

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

102112
// MARK: - Result
103113
extension Subprocess {
104-
public struct Result<T: Sendable>: Sendable {
114+
public struct ExecutionResult<T: Sendable>: Sendable {
105115
public let terminationStatus: TerminationStatus
106116
public let value: T
107117

@@ -142,9 +152,11 @@ extension Subprocess {
142152
}
143153
}
144154

145-
extension Subprocess.Result: Equatable where T : Equatable {}
155+
extension Subprocess.ExecutionResult: Equatable where T : Equatable {}
146156

147-
extension Subprocess.Result: Hashable where T : Hashable {}
157+
extension Subprocess.ExecutionResult: Hashable where T : Hashable {}
158+
159+
extension Subprocess.ExecutionResult: Codable where T : Codable {}
148160

149161
// MARK: Internal
150162
extension Subprocess {

Sources/_FoundationCShims/include/_FoundationCShims.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "bplist_shims.h"
2222
#include "io_shims.h"
2323
#include "platform_shims.h"
24+
#include "process_shims.h"
2425
#include "filemanager_shims.h"
2526
#include "uuid.h"
2627

Sources/_FoundationCShims/process_shims.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,22 @@ int _subprocess_fork_exec(
263263
}
264264

265265
// Bind stdin, stdout, and stderr
266-
int rc = dup2(file_descriptors[0], STDIN_FILENO);
267-
if (rc != 0) { return rc; }
268-
rc = dup2(file_descriptors[2], STDOUT_FILENO);
269-
if (rc != 0) { return rc; }
270-
rc = dup2(file_descriptors[4], STDERR_FILENO);
271-
if (rc != 0) { return rc; }
266+
int rc = 0;
267+
if (file_descriptors[0] != 0) {
268+
rc = dup2(file_descriptors[0], STDIN_FILENO);
269+
if (rc != 0) { return rc; }
270+
}
271+
if (file_descriptors[2] != 0) {
272+
rc = dup2(file_descriptors[2], STDOUT_FILENO);
273+
if (rc != 0) { return rc; }
274+
}
275+
276+
if (file_descriptors[4] != 0) {
277+
rc = dup2(file_descriptors[4], STDERR_FILENO);
278+
if (rc != 0) { return rc; }
279+
}
272280

281+
#warning Shold close all and then return error no early return
273282
// Close parent side
274283
if (file_descriptors[1] != 0) {
275284
rc = close(file_descriptors[1]);

0 commit comments

Comments
 (0)