diff --git a/Sources/ScipioKit/Executor.swift b/Sources/ScipioKit/Executor.swift index 79094f62..80f65961 100644 --- a/Sources/ScipioKit/Executor.swift +++ b/Sources/ScipioKit/Executor.swift @@ -11,8 +11,19 @@ private actor ProcessOutputBuffer { @_spi(Internals) public protocol Executor { + /// Executes the command with the given arguments and environment variables. + /// + /// - Parameters: + /// - arguments: Command-line arguments for the process. + /// - environment: Complete set of environment variables for the process. + /// If `nil`, the current environment is used. + /// If non-nil, it **replaces** the entire environment. + /// Use `ProcessInfo.processInfo.environment` to preserve existing values if needed. + /// + /// - Note: + /// This does not merge with the existing environment. @discardableResult - func execute(_ arguments: [String]) async throws -> ExecutorResult + func execute(_ arguments: [String], environment: [String: String]?) async throws -> ExecutorResult } @_spi(Internals) @@ -43,6 +54,11 @@ public protocol ExecutorResult: Sendable { } public extension Executor { + @discardableResult + func execute(_ arguments: [String]) async throws -> ExecutorResult { + try await execute(arguments, environment: nil) + } + @discardableResult func execute(_ arguments: String...) async throws -> ExecutorResult { try await execute(arguments) @@ -98,7 +114,7 @@ public struct ProcessExecutor: Executor, Sendable { public var streamOutput: (@Sendable ([UInt8]) async -> Void)? - public func execute(_ arguments: [String]) async throws -> ExecutorResult { + public func execute(_ arguments: [String], environment: [String: String]?) async throws -> ExecutorResult { guard let executable = arguments.first, !executable.isEmpty else { throw ProcessExecutorError.executableNotFound } @@ -108,7 +124,7 @@ public struct ProcessExecutor: Executor, Sendable { throw ProcessExecutorError.executableNotFound } - let process = Foundation.Process() + let process = Process() let stdoutPipe = Pipe() let stderrPipe = Pipe() @@ -117,6 +133,12 @@ public struct ProcessExecutor: Executor, Sendable { process.standardOutput = stdoutPipe process.standardError = stderrPipe + // Completely replace the process environment if provided. + // If nil, the process inherits the current environment. + if let environment { + process.environment = environment + } + let outputHandle = stdoutPipe.fileHandleForReading let errorHandle = stderrPipe.fileHandleForReading diff --git a/Tests/ScipioKitTests/TestingExecutor.swift b/Tests/ScipioKitTests/TestingExecutor.swift index 8c1d0bad..5a5a09f3 100644 --- a/Tests/ScipioKitTests/TestingExecutor.swift +++ b/Tests/ScipioKitTests/TestingExecutor.swift @@ -13,7 +13,7 @@ final class StubbableExecutor: Executor { calledArguments.count } - func execute(_ arguments: [String]) async throws -> ExecutorResult { + func execute(_ arguments: [String], environment: [String: String]?) async throws -> ExecutorResult { calledArguments.append(arguments) return try executeHook(arguments) }