From abbe19a91b74023ecb569d402ef707b219c45d67 Mon Sep 17 00:00:00 2001 From: mtj0928 Date: Mon, 2 Jun 2025 02:23:55 +0900 Subject: [PATCH 1/3] Support interactive execute --- Sources/NestKit/Utils/ProcessExecutor.swift | 38 ++++++++++++++++++- .../NestTestHelpers/MockExecutorBuilder.swift | 6 +++ Sources/nest/Commands/RunCommand.swift | 16 +++++--- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Sources/NestKit/Utils/ProcessExecutor.swift b/Sources/NestKit/Utils/ProcessExecutor.swift index 42722d8..46662b1 100644 --- a/Sources/NestKit/Utils/ProcessExecutor.swift +++ b/Sources/NestKit/Utils/ProcessExecutor.swift @@ -4,6 +4,12 @@ import os public protocol ProcessExecutor: Sendable { func execute(command: String, _ arguments: [String]) async throws -> String + + /// Executes the given command with the given arguments. + /// All inputs, outputs and errors are exposed to users unlike ``execute(command:_:)``. + /// So user can input texts if the command requires. + /// The returned value indicates the status of the results of the command. + func executeInteractively(command: String, _ arguments: [String]) async throws -> Int32 } extension ProcessExecutor { @@ -11,6 +17,10 @@ extension ProcessExecutor { try await execute(command: command, arguments) } + public func executeInteractively(command: String, _ arguments: String...) async throws -> Int32 { + try await executeInteractively(command: command, arguments) + } + public func which(_ command: String) async throws -> String { try await execute(command: "/usr/bin/which", command) } @@ -18,11 +28,18 @@ extension ProcessExecutor { public struct NestProcessExecutor: ProcessExecutor { let currentDirectoryURL: URL? + let environment: [String: String] let logger: Logging.Logger let logLevel: Logging.Logger.Level - public init(currentDirectory: URL? = nil, logger: Logging.Logger, logLevel: Logging.Logger.Level = .debug) { + public init( + currentDirectory: URL? = nil, + environment: [String: String] = ProcessInfo.processInfo.environment, + logger: Logging.Logger, + logLevel: Logging.Logger.Level = .debug, + ) { self.currentDirectoryURL = currentDirectory + self.environment = environment self.logger = logger self.logLevel = logLevel } @@ -48,6 +65,7 @@ public struct NestProcessExecutor: ProcessExecutor { process.currentDirectoryURL = currentDirectoryURL process.executableURL = executableURL process.arguments = arguments + process.environment = environment let outputPipe = Pipe() process.standardOutput = outputPipe @@ -96,6 +114,24 @@ public struct NestProcessExecutor: ProcessExecutor { } } } + + public func executeInteractively(command: String, _ arguments: [String]) async throws -> Int32 { + logger.debug("$ \(command) \(arguments.joined(separator: " "))") + + let process = Process() + process.executableURL = URL(fileURLWithPath: command) + process.arguments = arguments + process.environment = environment + + if let currentDirectoryURL { + process.currentDirectoryURL = currentDirectoryURL + } + + try process.run() + tcsetpgrp(STDIN_FILENO, process.processIdentifier) + process.waitUntilExit() + return process.terminationStatus + } } enum StreamElement { diff --git a/Sources/NestTestHelpers/MockExecutorBuilder.swift b/Sources/NestTestHelpers/MockExecutorBuilder.swift index 1f5a650..ca7532d 100644 --- a/Sources/NestTestHelpers/MockExecutorBuilder.swift +++ b/Sources/NestTestHelpers/MockExecutorBuilder.swift @@ -46,4 +46,10 @@ public struct MockProcessExecutor: ProcessExecutor { public func execute(command: String, _ arguments: [String]) async throws -> String { try await executorClosure(command, arguments) } + + public func executeInteractively(command: String, _ arguments: [String]) async throws -> Int32 { + // For testing, just call execute and exit + _ = try await executorClosure(command, arguments) + return 0 + } } diff --git a/Sources/nest/Commands/RunCommand.swift b/Sources/nest/Commands/RunCommand.swift index b3c44e3..9b643c1 100644 --- a/Sources/nest/Commands/RunCommand.swift +++ b/Sources/nest/Commands/RunCommand.swift @@ -83,12 +83,16 @@ struct RunCommand: AsyncParsableCommand { return } - let binaryRelativePath = executables[0].binaryPath // FIXME: Needs to address multiple commands in the same artifact bundle. - _ = try? await NestProcessExecutor(logger: logger, logLevel: .info) - .execute( - command: nestDirectory.rootDirectory.appending(path: binaryRelativePath.path(percentEncoded: false)).path(percentEncoded: false), - subcommand.arguments - ) + // FIXME: Needs to address multiple commands in the same artifact bundle. + let binaryRelativePath = executables[0].binaryPath.path(percentEncoded: false) + let command = nestDirectory.rootDirectory.appending(path: binaryRelativePath).path(percentEncoded: false) + var environment = ProcessInfo.processInfo.environment + environment["RESOURCE_PATH"] = "" + let result = try await NestProcessExecutor(environment: environment, logger: logger, logLevel: .info) + .executeInteractively(command: command, subcommand.arguments) + if result != 0 { + Foundation.exit(result) + } } } From cc6fd011def4e033dbd41adba790e49ac88bf446 Mon Sep 17 00:00:00 2001 From: mtj0928 Date: Tue, 3 Jun 2025 22:31:22 +0900 Subject: [PATCH 2/3] Fix build error on Swift 6.0 --- Sources/NestKit/Utils/ProcessExecutor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NestKit/Utils/ProcessExecutor.swift b/Sources/NestKit/Utils/ProcessExecutor.swift index 46662b1..f9153cb 100644 --- a/Sources/NestKit/Utils/ProcessExecutor.swift +++ b/Sources/NestKit/Utils/ProcessExecutor.swift @@ -36,7 +36,7 @@ public struct NestProcessExecutor: ProcessExecutor { currentDirectory: URL? = nil, environment: [String: String] = ProcessInfo.processInfo.environment, logger: Logging.Logger, - logLevel: Logging.Logger.Level = .debug, + logLevel: Logging.Logger.Level = .debug ) { self.currentDirectoryURL = currentDirectory self.environment = environment From 5baf9440eb93ef7e7cbeaf0b43f5a385a34ee366 Mon Sep 17 00:00:00 2001 From: mtj0928 Date: Tue, 3 Jun 2025 22:31:56 +0900 Subject: [PATCH 3/3] Add comment --- Sources/NestKit/Utils/ProcessExecutor.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/NestKit/Utils/ProcessExecutor.swift b/Sources/NestKit/Utils/ProcessExecutor.swift index f9153cb..6c08b0e 100644 --- a/Sources/NestKit/Utils/ProcessExecutor.swift +++ b/Sources/NestKit/Utils/ProcessExecutor.swift @@ -128,7 +128,11 @@ public struct NestProcessExecutor: ProcessExecutor { } try process.run() + + // Need to support standard input + // https://forums.swift.org/t/how-to-allow-process-to-receive-user-input-when-run-as-part-of-an-executable-e-g-to-enabled-sudo-commands/34357/7 tcsetpgrp(STDIN_FILENO, process.processIdentifier) + process.waitUntilExit() return process.terminationStatus }