diff --git a/Sources/SwiftDriver/Toolchains/Toolchain.swift b/Sources/SwiftDriver/Toolchains/Toolchain.swift index 214ff8597..7f97686b3 100644 --- a/Sources/SwiftDriver/Toolchains/Toolchain.swift +++ b/Sources/SwiftDriver/Toolchains/Toolchain.swift @@ -15,6 +15,7 @@ import class Foundation.Bundle import func TSCBasic.getEnvSearchPaths import func TSCBasic.lookupExecutablePath +import protocol TSCBasic.DiagnosticData import class TSCBasic.DiagnosticsEngine import protocol TSCBasic.FileSystem import struct TSCBasic.AbsolutePath @@ -215,17 +216,25 @@ extension Toolchain { /// looks in the `executableDir`, `xcrunFind` or in the `searchPaths`. /// - Parameter executable: executable to look for [i.e. `swift`]. Executable suffix (eg. `.exe`) should be omitted. func lookup(executable: String) throws -> AbsolutePath { + // 1. Check the `SWIFT_DRIVER__EXEC` override. if let overrideString = envVar(forExecutable: executable), let path = try? AbsolutePath(validating: overrideString) { + if !fallbackToExecutableDefaultPath && !fileSystem.isExecutableFile(path) { + throw ToolchainError.notAValidExecutablePath(path.pathString) + } return path + // 2. If `-tools-directory` is set, check there. } else if let toolDir = toolDirectory, let path = lookupExecutablePath(filename: executableName(executable), currentWorkingDirectory: nil, searchPaths: [toolDir]) { - // Looking for tools from the tools directory. return path + // 3. Perform lookup relative to the driver's executable } else if let path = lookupExecutablePath(filename: executableName(executable), currentWorkingDirectory: nil, searchPaths: [try executableDir]) { return path + // 4. Attempt lookup with `xcrun --find`. } else if let path = try? xcrunFind(executable: executableName(executable)) { return path + // 5. If querying not the compiler frontend itself and the above attempts failed, + // attempt to resolve adjacent to the compiler frontend. } else if !["swift-frontend", "swift"].contains(executable), let parentDirectory = try? getToolPath(.swiftCompiler).parentDirectory, try parentDirectory != executableDir, @@ -233,11 +242,15 @@ extension Toolchain { // If the driver library's client and the frontend are in different directories, // try looking for tools next to the frontend. return path + // 6. Perform lookup in the toolchain search paths (e.g. $PATH) } else if let path = lookupExecutablePath(filename: executableName(executable), searchPaths: searchPaths) { return path + // 7. Attempt lookup of `swift` for the compiler frontned + // FIXME: we should remove this now } else if executable == "swift-frontend" { // Temporary shim: fall back to looking for "swift" before failing. return try lookup(executable: "swift") + // 8. For testing purposes, attempt lookup in the system "default" paths } else if fallbackToExecutableDefaultPath { if self is WindowsToolchain { return try getToolPath(.swiftCompiler) @@ -247,7 +260,7 @@ extension Toolchain { return try AbsolutePath(validating: "/usr/bin/" + executable) } } else { - throw ToolchainError.unableToFind(tool: executable) + throw ToolchainError.unableToFind(executable) } } @@ -302,7 +315,7 @@ extension Toolchain { private func xcrunFind(executable: String) throws -> AbsolutePath { let xcrun = "xcrun" guard lookupExecutablePath(filename: xcrun, searchPaths: searchPaths) != nil else { - throw ToolchainError.unableToFind(tool: xcrun) + throw ToolchainError.unableToFind(xcrun) } let path = try executor.checkNonZeroExit( @@ -368,6 +381,17 @@ extension Toolchain { } } -@_spi(Testing) public enum ToolchainError: Swift.Error { - case unableToFind(tool: String) +@_spi(Testing) public enum ToolchainError: Swift.Error, Equatable, DiagnosticData { + case unableToFind(String) + case notAValidExecutablePath(String) + + public var description: String { + switch self { + case .unableToFind(let tool): + return "unable to locate tool: '\(tool)'" + case .notAValidExecutablePath(let path): + return "not a valid executable: \(path)" + + } + } } diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index b42c446ad..48613a02d 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -79,6 +79,7 @@ final class SwiftDriverTests: XCTestCase { // so there is no swift-help in the toolchain yet. Set the environment variable // as if we had found it for the purposes of testing build planning. var env = ProcessEnv.vars + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_SWIFT_HELP_EXEC"] = "/tmp/.test-swift-help" return env } @@ -2387,6 +2388,7 @@ final class SwiftDriverTests: XCTestCase { func testWebAssemblyUnsupportedFeatures() throws { var env = ProcessEnv.vars + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_SWIFT_AUTOLINK_EXTRACT_EXEC"] = "/garbage/swift-autolink-extract" do { var driver = try Driver(args: ["swift", "-target", "wasm32-unknown-wasi", "foo.swift"], env: env) @@ -3140,7 +3142,7 @@ final class SwiftDriverTests: XCTestCase { "-emit-library", "-driver-filelist-threshold=0" ]) - var jobs = try driver.planBuild() + let jobs = try driver.planBuild() XCTAssertEqual(jobs.count, 4) XCTAssertEqual(getFileListElements(for: "-filelist", job: jobs[2]), [.temporary(try .init(validating: "hello-1.o"))]) @@ -4433,6 +4435,7 @@ final class SwiftDriverTests: XCTestCase { // Drop SWIFT_DRIVER_CLANG_EXEC from the environment so it doesn't // interfere with tool lookup. var env = ProcessEnv.vars + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env.removeValue(forKey: "SWIFT_DRIVER_CLANG_EXEC") var driver = try Driver(args: ["swiftc", @@ -4837,6 +4840,7 @@ final class SwiftDriverTests: XCTestCase { // As per Unix conventions, /var/empty is expected to exist and be empty. // This gives us a non-existent path that we can use for libtool which // allows us to run this this on non-Darwin platforms. + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_LIBTOOL_EXEC"] = "/var/empty/libtool" // No dSYM generation (-g -emit-library -static) @@ -6696,6 +6700,7 @@ final class SwiftDriverTests: XCTestCase { func testEmbeddedSwiftOptions() throws { var env = ProcessEnv.vars + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_SWIFT_AUTOLINK_EXTRACT_EXEC"] = "/garbage/swift-autolink-extract" do { @@ -6813,6 +6818,7 @@ final class SwiftDriverTests: XCTestCase { // better override. var env = ProcessEnv.vars let swiftHelp: AbsolutePath = try AbsolutePath(validating: "/usr/bin/nonexistent-swift-help") + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_SWIFT_HELP_EXEC"] = swiftHelp.pathString env["SWIFT_DRIVER_CLANG_EXEC"] = "/usr/bin/clang" var driver = try Driver( @@ -6826,6 +6832,7 @@ final class SwiftDriverTests: XCTestCase { func testSwiftClangOverride() throws { var env = ProcessEnv.vars let swiftClang = try AbsolutePath(validating: "/A/Path/swift-clang") + env["SWIFT_DRIVER_TESTS_ENABLE_EXEC_PATH_FALLBACK"] = "1" env["SWIFT_DRIVER_CLANG_EXEC"] = swiftClang.pathString var driver = try Driver(