diff --git a/Package.resolved b/Package.resolved index 0a8bf8f..72ad4c5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "42117a24adae9d77816b090d33a4ef9548125c0f3ef95e49547663ad6064444d", + "originHash" : "346525d1087751bbdce931b44a043368833b77cb6295f31ab26d229cb8e18b9a", "pins" : [ { "identity" : "path", "kind" : "remoteSourceControl", "location" : "https://github.com/tuist/Path", "state" : { - "revision" : "4490da629937fc3994f72dd787dcc3f50d6973fe", - "version" : "0.3.0" + "revision" : "f63466272e0ae49b0ab6228afe39b113c98f350a", + "version" : "0.3.2" } }, { diff --git a/Sources/Command/Command.swift b/Sources/Command/Command.swift index 468287f..1dc80c2 100644 --- a/Sources/Command/Command.swift +++ b/Sources/Command/Command.swift @@ -100,18 +100,15 @@ public enum CommandEvent: Sendable { public enum CommandError: Error, CustomStringConvertible, Sendable { case terminated(Int32, stderr: String) case signalled(Int32) - case errorObtainingExecutable(executable: String, error: String) case executableNotFound(String) + case missingExecutableName public var description: String { switch self { case let .signalled(code): return "The command terminated after receiving a signal with code \(code)" case let .terminated(code, _): return "The command terminated with the code \(code)" - case let .errorObtainingExecutable( - name, - error - ): return "There was an error trying to obtain the path to the executable '\(name)': \(error)" case let .executableNotFound(name): return "Couldn't locate the executable '\(name)' in the environment." + case .missingExecutableName: return "The executable name is missing." } } } @@ -179,9 +176,7 @@ public struct CommandRunner: CommandRunning, Sendable { let executable = try lookupExecutable(firstArgument: arguments.first) process.executableURL = executable - if let executable { - logger?.debug("Running command: \(executable.absoluteString) \(processArguments.joined(separator: " "))") - } + logger?.debug("Running command: \(executable.absoluteString) \(processArguments.joined(separator: " "))") let threadSafeProcess = ThreadSafe(process) @@ -238,8 +233,10 @@ public struct CommandRunner: CommandRunning, Sendable { } } - func lookupExecutable(firstArgument: String?) throws -> URL? { - guard let firstArgument else { return nil } + func lookupExecutable(firstArgument: String?) throws -> URL { + guard let firstArgument else { + throw CommandError.missingExecutableName + } // If the first argument is an absolute URL to an executable, return it. if let executablePath = try? Path.AbsolutePath(validating: firstArgument) { @@ -250,7 +247,7 @@ public struct CommandRunner: CommandRunning, Sendable { let arguments: [String] #if os(Windows) - command = "where" + command = "C:\\Windows\\System32\\where.exe" arguments = [firstArgument] #else command = "/usr/bin/which" @@ -265,27 +262,23 @@ public struct CommandRunner: CommandRunning, Sendable { let process = Process() process.executableURL = URL(fileURLWithPath: command) process.arguments = arguments + process.environment = ProcessInfo.processInfo.environment + process.currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) let pipe = Pipe() process.standardOutput = pipe process.standardError = pipe - do { - try process.run() - } catch { - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" - throw CommandError.errorObtainingExecutable(executable: firstArgument, error: output) - } - - let data = pipe.fileHandleForReading.readDataToEndOfFile() + try process.run() process.waitUntilExit() - if let output = String(data: data, encoding: .utf8) { - let trimmedOutput = output.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedOutput.isEmpty ? nil : URL(fileURLWithPath: trimmedOutput) + let data = try pipe.fileHandleForReading.readToEnd() + let output = String(data: data ?? .init(), encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + + guard let output, !output.isEmpty else { + throw CommandError.executableNotFound(firstArgument) } - return nil + return URL(fileURLWithPath: output) } } diff --git a/Tests/CommandTests/CommandTests.swift b/Tests/CommandTests/CommandTests.swift index 567f351..472f443 100644 --- a/Tests/CommandTests/CommandTests.swift +++ b/Tests/CommandTests/CommandTests.swift @@ -22,7 +22,7 @@ final class CommandTests: XCTestCase { let executableURL = try commandRunner.lookupExecutable(firstArgument: absolutePath) // Then - XCTAssertEqual(executableURL?.path, absolutePath) + XCTAssertEqual(executableURL.path, absolutePath) } func test_lookupExecutable_withRegularCommand() throws { @@ -34,8 +34,7 @@ final class CommandTests: XCTestCase { let executableURL = try commandRunner.lookupExecutable(firstArgument: command) // Then - XCTAssertNotNil(executableURL) - XCTAssertTrue(executableURL!.path.hasSuffix("/\(command)")) + XCTAssertTrue(executableURL.path.hasSuffix("/\(command)")) } func test_lookupExecutable_withInvalidCommand() throws { @@ -43,10 +42,15 @@ final class CommandTests: XCTestCase { let commandRunner = CommandRunner() let command = "nonexistentcommand" - // When - let executableURL = try commandRunner.lookupExecutable(firstArgument: command) + // When & Then + XCTAssertThrowsError(try commandRunner.lookupExecutable(firstArgument: command)) + } - // Then - XCTAssertNil(executableURL) + func test_lookupExecutable_withMissingExecutableCommand() throws { + // Given + let commandRunner = CommandRunner() + + // When & Then + XCTAssertThrowsError(try commandRunner.lookupExecutable(firstArgument: nil)) } }