diff --git a/Sources/SWBUtil/Process.swift b/Sources/SWBUtil/Process.swift index d832eb5a..10a18558 100644 --- a/Sources/SWBUtil/Process.swift +++ b/Sources/SWBUtil/Process.swift @@ -82,61 +82,73 @@ extension Process { extension Process { public static func getOutput(url: URL, arguments: [String], currentDirectoryURL: URL? = nil, environment: Environment? = nil, interruptible: Bool = true) async throws -> Processes.ExecutionResult { if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) { + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + // Extend the lifetime of the pipes to avoid file descriptors being closed until the AsyncStream is finished being consumed. - return try await withExtendedLifetime((Pipe(), Pipe())) { (stdoutPipe, stderrPipe) in - let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in - let stdoutStream = process.makeStream(for: \.standardOutputPipe, using: stdoutPipe) - let stderrStream = process.makeStream(for: \.standardErrorPipe, using: stderrPipe) - return (stdoutStream, stderrStream) - } collect: { (stdoutStream, stderrStream) in - let stdoutData = try await stdoutStream.collect() - let stderrData = try await stderrStream.collect() - return (stdoutData: stdoutData, stderrData: stderrData) - } - return Processes.ExecutionResult(exitStatus: exitStatus, stdout: Data(output.stdoutData), stderr: Data(output.stderrData)) + defer { withExtendedLifetime(stdoutPipe) {} } + defer { withExtendedLifetime(stderrPipe) {} } + + let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in + let stdoutStream = process.makeStream(for: \.standardOutputPipe, using: stdoutPipe) + let stderrStream = process.makeStream(for: \.standardErrorPipe, using: stderrPipe) + return (stdoutStream, stderrStream) + } collect: { (stdoutStream, stderrStream) in + let stdoutData = try await stdoutStream.collect() + let stderrData = try await stderrStream.collect() + return (stdoutData: stdoutData, stderrData: stderrData) } + return Processes.ExecutionResult(exitStatus: exitStatus, stdout: Data(output.stdoutData), stderr: Data(output.stderrData)) } else { + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + // Extend the lifetime of the pipes to avoid file descriptors being closed until the AsyncStream is finished being consumed. - return try await withExtendedLifetime((Pipe(), Pipe())) { (stdoutPipe, stderrPipe) in - let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in - let stdoutStream = process._makeStream(for: \.standardOutputPipe, using: stdoutPipe) - let stderrStream = process._makeStream(for: \.standardErrorPipe, using: stderrPipe) - return (stdoutStream, stderrStream) - } collect: { (stdoutStream, stderrStream) in - let stdoutData = try await stdoutStream.collect() - let stderrData = try await stderrStream.collect() - return (stdoutData: stdoutData, stderrData: stderrData) - } - return Processes.ExecutionResult(exitStatus: exitStatus, stdout: Data(output.stdoutData), stderr: Data(output.stderrData)) + defer { withExtendedLifetime(stdoutPipe) {} } + defer { withExtendedLifetime(stderrPipe) {} } + + let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in + let stdoutStream = process._makeStream(for: \.standardOutputPipe, using: stdoutPipe) + let stderrStream = process._makeStream(for: \.standardErrorPipe, using: stderrPipe) + return (stdoutStream, stderrStream) + } collect: { (stdoutStream, stderrStream) in + let stdoutData = try await stdoutStream.collect() + let stderrData = try await stderrStream.collect() + return (stdoutData: stdoutData, stderrData: stderrData) } + return Processes.ExecutionResult(exitStatus: exitStatus, stdout: Data(output.stdoutData), stderr: Data(output.stderrData)) } } public static func getMergedOutput(url: URL, arguments: [String], currentDirectoryURL: URL? = nil, environment: Environment? = nil, interruptible: Bool = true) async throws -> (exitStatus: Processes.ExitStatus, output: Data) { if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) { - // Extend the lifetime of the pipe to avoid file descriptors being closed until the AsyncStream is finished being consumed. - return try await withExtendedLifetime(Pipe()) { pipe in - let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in - process.standardOutputPipe = pipe - process.standardErrorPipe = pipe - return pipe.fileHandleForReading.bytes(on: .global()) - } collect: { stream in - try await stream.collect() - } - return (exitStatus: exitStatus, output: Data(output)) + let pipe = Pipe() + + // Extend the lifetime of the pipes to avoid file descriptors being closed until the AsyncStream is finished being consumed. + defer { withExtendedLifetime(pipe) {} } + + let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in + process.standardOutputPipe = pipe + process.standardErrorPipe = pipe + return pipe.fileHandleForReading.bytes(on: .global()) + } collect: { stream in + try await stream.collect() } + return (exitStatus: exitStatus, output: Data(output)) } else { - // Extend the lifetime of the pipe to avoid file descriptors being closed until the AsyncStream is finished being consumed. - return try await withExtendedLifetime(Pipe()) { pipe in - let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in - process.standardOutputPipe = pipe - process.standardErrorPipe = pipe - return pipe.fileHandleForReading._bytes(on: .global()) - } collect: { stream in - try await stream.collect() - } - return (exitStatus: exitStatus, output: Data(output)) + let pipe = Pipe() + + // Extend the lifetime of the pipes to avoid file descriptors being closed until the AsyncStream is finished being consumed. + defer { withExtendedLifetime(pipe) {} } + + let (exitStatus, output) = try await _getOutput(url: url, arguments: arguments, currentDirectoryURL: currentDirectoryURL, environment: environment, interruptible: interruptible) { process in + process.standardOutputPipe = pipe + process.standardErrorPipe = pipe + return pipe.fileHandleForReading._bytes(on: .global()) + } collect: { stream in + try await stream.collect() } + return (exitStatus: exitStatus, output: Data(output)) } }