Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ By analogy with Shell, you can make other wrappers over the `ProcessCommand`
Using simctl, you can search for iPhone 12, turn it on and launch the application.

```swift
let xcrun = XCRun()
let simulator = xcrun.simctl
let xcrun = XCRun.current
let simulator = try await xcrun.simctl

let list = try await simulator.list(.devices, "iPhone 12", available: true).json.decode()
let devices = list.devices.flatMap { $0.value }
Expand Down
3 changes: 0 additions & 3 deletions Sources/XSTooling/ProcessCommand+Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ extension ProcessCommand {

extension Process {
fileprivate func perform() async throws {
try Task.checkCancellation()

try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
self.terminationHandler = { process in
Expand All @@ -66,7 +64,6 @@ extension Process {
self.terminate() // crash if not running
}
}
try Task.checkCancellation()
}
}

Expand Down
63 changes: 38 additions & 25 deletions Sources/XSTooling/ProcessCommand+Subprocess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,47 @@ extension ProcessCommand {
}

func readSubprocess(standardError: FileHandle?, limit: Int) async throws -> ProcessOutput {
let values: (terminationStatus: TerminationStatus, standardOutput: Data)
if standardError == FileHandle.standardOutput {
let result = try await Subprocess::run(
self.subprocessConfiguration,
output: .data(limit: limit),
error: .combinedWithOutput,
)
values = (result.terminationStatus, result.standardOutput)
} else {
let result = try await Subprocess::run(
self.subprocessConfiguration,
output: .data(limit: limit),
error: standardError?.fileDescriptorOutput ?? .currentStandardError,
)
values = (result.terminationStatus, result.standardOutput)
}
try check(terminationStatus: values.terminationStatus)
return ProcessOutput(data: values.standardOutput)
let config = self.subprocessConfiguration
let output = DataOutput.data(limit: limit)
lazy var error = standardError?.fileDescriptorOutput ?? .currentStandardError

let (terminationStatus, standardOutput) =
switch standardError {
case .standardOutput:
try await Subprocess::run(config, output: output, error: .combinedWithOutput).dataOutput
case .nullDevice:
try await Subprocess::run(config, output: output, error: .discarded).dataOutput
default:
try await Subprocess::run(config, output: output, error: error).dataOutput
}
try check(terminationStatus: terminationStatus)
return ProcessOutput(data: standardOutput)
}

func runSubprocess(
standardOutput: FileHandle?,
standardError: FileHandle?,
) async throws {
let result = try await Subprocess::run(
self.subprocessConfiguration,
output: standardOutput?.fileDescriptorOutput ?? .currentStandardOutput,
error: standardError?.fileDescriptorOutput ?? .currentStandardError,
)
try check(terminationStatus: result.terminationStatus)

let config = self.subprocessConfiguration
lazy var output = standardOutput?.fileDescriptorOutput ?? .currentStandardOutput
lazy var error = standardError?.fileDescriptorOutput ?? .currentStandardError

let status =
switch (standardOutput, standardError) {
case (.nullDevice, .nullDevice):
try await Subprocess::run(config, output: .discarded, error: .discarded).terminationStatus
case (_, .nullDevice):
try await Subprocess::run(config, output: output, error: .discarded).terminationStatus
case (.nullDevice, _):
try await Subprocess::run(config, output: .discarded, error: error).terminationStatus
default:
try await Subprocess::run(config, output: output, error: error).terminationStatus
}
try check(terminationStatus: status)
}

private func check(terminationStatus: TerminationStatus) throws {
try Task.checkCancellation()
if terminationStatus.isSuccess {
return
}
Expand Down Expand Up @@ -97,6 +104,12 @@ extension TerminationStatus {
}
}

extension ExecutionResult where Output == DataOutput {
fileprivate var dataOutput: (TerminationStatus, Data) {
(terminationStatus, standardOutput)
}
}

extension FileHandle {
fileprivate var fileDescriptorOutput: FileDescriptorOutput {
.fileDescriptor(
Expand Down
54 changes: 4 additions & 50 deletions Sources/XSTooling/ProcessCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,16 @@ public struct ProcessCommand: Hashable, Sendable {
// MARK: - Running

public func read(standardError: FileHandle? = nil) async throws -> ProcessOutput {
try Task.checkCancellation()
#if EnableSubprocess
try await readSubprocess(standardError: standardError, limit: Int.max)
return try await readSubprocess(standardError: standardError, limit: Int.max)
#else
try await readProcess(standardError: standardError)
return try await readProcess(standardError: standardError)
#endif
}

public func run(standardOutput: FileHandle? = nil, standardError: FileHandle? = nil) async throws {
try Task.checkCancellation()
#if EnableSubprocess
try await runSubprocess(standardOutput: standardOutput, standardError: standardError)
#else
Expand Down Expand Up @@ -135,51 +137,3 @@ public struct ProcessError: Error, Equatable {
self.terminationReason = terminationReason
}
}

// MARK: - Process

extension Process {
fileprivate func execute() async throws {
try Task.checkCancellation()

try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
self.terminationHandler = { process in
process.terminationHandler = nil
continuation.resume()
}
do {
try self.run()
} catch {
self.terminationHandler = nil
continuation.resume(throwing: error)
}
}
} onCancel: { // can be canceled without starting
if self.isRunning {
self.terminate() // crash if not running
}
}
try Task.checkCancellation()
}
}

// MARK: - FileHandle + AsyncStream

extension FileHandle {
fileprivate func stream() -> AsyncStream<Data> {
AsyncStream<Data> { continuation in
continuation.onTermination = { [weak self] _ in
self?.readabilityHandler = nil // stop
}
self.readabilityHandler = { fileHandle in
let data = fileHandle.availableData
if data.isEmpty {
continuation.finish()
} else {
continuation.yield(data)
}
}
}
}
}
14 changes: 0 additions & 14 deletions Sources/XSTooling/Shell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,6 @@ public struct Shell: Equatable, Sendable {
self.arguments = arguments
}

// MARK: - Options

public var verbose: Shell { option("--verbose") }

public var login: Shell { option("--login") }

private func option(_ value: String) -> Shell {
var shell = self
shell.arguments.append(value)
return shell
}

// MARK: - Commands

/// Show version information for this instance of bash on the standard output and exit successfully.
public var version: ProcessCommand {
ProcessCommand(path: path, arguments: arguments).appending(argument: "--version")
Expand Down
135 changes: 0 additions & 135 deletions Tests/XSToolingTests/Core/ProcessCommandTests.swift

This file was deleted.

21 changes: 0 additions & 21 deletions Tests/XSToolingTests/Core/ProcessOutputTests.swift

This file was deleted.

23 changes: 0 additions & 23 deletions Tests/XSToolingTests/GHTest/GHActions.swift

This file was deleted.

Loading
Loading