Skip to content

[Toolchains] - Generalize lookup routines as a Toolchain extension. #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 10 additions & 32 deletions Sources/SwiftDriver/Toolchains/DarwinToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,30 @@
//===----------------------------------------------------------------------===//
import TSCBasic

fileprivate func envVarName(forExecutable toolName: String) -> String {
return "SWIFT_DRIVER_\(toolName.uppercased())_EXEC"
}

/// Toolchain for Darwin-based platforms, such as macOS and iOS.
///
/// FIXME: This class is not thread-safe.
public final class DarwinToolchain: Toolchain {
public let env: [String: String]

func xcrunFind(exec: String) throws -> AbsolutePath {
if let overrideString = env[envVarName(forExecutable: exec)] {
return try AbsolutePath(validating: overrideString)
}

#if os(macOS)
let path = try Process.checkNonZeroExit(
arguments: ["xcrun", "-sdk", "macosx", "--find", exec],
environment: env
).spm_chomp()
return AbsolutePath(path)
#else
// This is a hack so our tests work on linux. We need a better way for looking up tools in general.
return AbsolutePath("/usr/bin/" + exec)
#endif

public init(env: [String: String]) {
self.env = env
}

/// Retrieve the absolute path for a given tool.
public func getToolPath(_ tool: Tool) throws -> AbsolutePath {
switch tool {
case .swiftCompiler:
return try xcrunFind(exec: "swift")
return try lookup(exec: "swift")

case .dynamicLinker:
return try xcrunFind(exec: "ld")
return try lookup(exec: "ld")

case .staticLinker:
return try xcrunFind(exec: "libtool")
return try lookup(exec: "libtool")

case .dsymutil:
return try xcrunFind(exec: "dsymutil")
return try lookup(exec: "dsymutil")

case .clang:
let result = try Process.checkNonZeroExit(
Expand All @@ -60,13 +43,13 @@ public final class DarwinToolchain: Toolchain {
).spm_chomp()
return AbsolutePath(result)
case .swiftAutolinkExtract:
return try xcrunFind(exec: "swift-autolink-extract")
return try lookup(exec: "swift-autolink-extract")
}
}

/// Swift compiler path.
public lazy var swiftCompiler: Result<AbsolutePath, Swift.Error> = Result {
try xcrunFind(exec: "swift")
try lookup(exec: "swift")
}

/// SDK path.
Expand All @@ -88,11 +71,6 @@ public final class DarwinToolchain: Toolchain {
return swiftCompiler.map{ $0.appending(RelativePath("../../lib/swift/macosx")) }
}


public init(env: [String: String]) {
self.env = env
}

public func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String {
switch type {
case .executable: return moduleName
Expand Down
22 changes: 0 additions & 22 deletions Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,10 @@ import TSCBasic

/// Toolchain for Unix-like systems.
public final class GenericUnixToolchain: Toolchain {
enum Error: Swift.Error {
case unableToFind(tool: String)
}

public let env: [String: String]

private let searchPaths: [AbsolutePath]

public init(env: [String: String]) {
self.env = env
self.searchPaths = getEnvSearchPaths(pathString: env["PATH"], currentWorkingDirectory: localFileSystem.currentWorkingDirectory)
}

private func lookup(exec: String) throws -> AbsolutePath {
if let path = lookupExecutablePath(filename: exec, searchPaths: searchPaths) {
return path
}

// If we happen to be on a macOS host, some tools might not be in our
// PATH, so we'll just use xcrun to find them too.
#if os(macOS)
return try DarwinToolchain(env: self.env).xcrunFind(exec: exec)
#else
throw Error.unableToFind(tool: exec)
#endif

}

public func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String {
Expand Down
60 changes: 60 additions & 0 deletions Sources/SwiftDriver/Toolchains/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import TSCBasic

public enum Tool {
Expand All @@ -27,6 +28,8 @@ public protocol Toolchain {

var env: [String: String] { get }

var searchPaths: [AbsolutePath] { get }

/// Retrieve the absolute path to a particular tool.
func getToolPath(_ tool: Tool) throws -> AbsolutePath

Expand Down Expand Up @@ -59,6 +62,10 @@ public protocol Toolchain {
}

extension Toolchain {
public var searchPaths: [AbsolutePath] {
getEnvSearchPaths(pathString: env["PATH"], currentWorkingDirectory: localFileSystem.currentWorkingDirectory)
}

public func swiftCompilerVersion() throws -> String {
try Process.checkNonZeroExit(
args: getToolPath(.swiftCompiler).pathString, "-version",
Expand All @@ -74,4 +81,57 @@ extension Toolchain {
).spm_chomp()
return Triple(triple)
}

/// Returns the `executablePath`'s directory.
public var executableDir: AbsolutePath {
guard let path = Bundle.main.executablePath else {
fatalError("Could not find executable path.")
}
return AbsolutePath(path).parentDirectory
}

/// Looks for `SWIFT_DRIVER_TOOLNAME_EXEC` in the `env` property.
/// - Returns: Environment variable value, if any.
func envVar(forExecutable toolName: String) -> String? {
return env[envVarName(for: toolName)]
}

/// - Returns: String in the form of: `SWIFT_DRIVER_TOOLNAME_EXEC`
private func envVarName(for toolName: String) -> String {
return "SWIFT_DRIVER_\(toolName.uppercased())_EXEC"
}

/// Looks for the executable in the `SWIFT_DRIVER_TOOLNAME_EXEC` enviroment variable, if found nothing,
/// looks in the `executableDir`, `xcrunFind` or in the `searchPaths`.
/// - Parameter exec: executable to look for [i.e. `swift`].
func lookup(exec: String) throws -> AbsolutePath {
if let overrideString = envVar(forExecutable: exec) {
return try AbsolutePath(validating: overrideString)
} else if let path = lookupExecutablePath(filename: exec, searchPaths: [executableDir]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be lookupExecutablePath(filename: exec, searchPaths: [executableDir] + searchPaths)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for searchPaths in macOS was crashing. Later today I will dig into it to find the real reason and will provide a detailed response or the fix.

Copy link
Contributor Author

@JhonnyBillM JhonnyBillM Nov 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue here is that after we find the swift compiler path in the searchPaths, we cannot find the resource directory under ~/usr/lib/swift/.

is macOS supposed to have a resource directory at ~/usr/lib/swift/, or what is the correct way to handle this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the resource directory is in the Xcode toolchain on macOS, inside /Applications/Xcode.app/Contents/Developer/Toolchains/<dir>.xctoolchain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we first look in the executable path, then go to xcrun, and finally look into the searchPaths.

This is the "best" approach right now because in macOS the toolchain lives under Xcode, not under the Swift executable in ~/usr/lib/swift/.

return path
} else if let path = try? xcrunFind(exec: exec) {
return path
} else if let path = lookupExecutablePath(filename: exec, searchPaths: searchPaths) {
return path
} else {
// This is a hack so our tests work on linux. We need a better way for looking up tools in general.
return AbsolutePath("/usr/bin/" + exec)
}
}

private func xcrunFind(exec: String) throws -> AbsolutePath {
#if os(macOS)
let path = try Process.checkNonZeroExit(
arguments: ["xcrun", "-sdk", "macosx", "--find", exec],
environment: env
).spm_chomp()
return AbsolutePath(path)
#else
throw ToolchainError.unableToFind(tool: exec)
#endif
}
}

fileprivate enum ToolchainError: Swift.Error {
case unableToFind(tool: String)
}
24 changes: 17 additions & 7 deletions Tests/SwiftDriverTests/JobExecutorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,25 @@ final class JobExecutorTests: XCTestCase {

func testSwiftDriverExecOverride() throws {
var env = ProcessEnv.vars
let envVarName = "SWIFT_DRIVER_SWIFT_EXEC"
let dummyPath = "/some/garbage/path/fnord"

env.removeValue(forKey: "SWIFT_DRIVER_SWIFT_EXEC")
let normalToolchain = DarwinToolchain(env: env)
let normalSwiftPath = try normalToolchain.getToolPath(.swiftCompiler)
// DarwinToolchain
env.removeValue(forKey: envVarName)
let normalSwiftPath = try DarwinToolchain(env: env).getToolPath(.swiftCompiler)
XCTAssertEqual(normalSwiftPath.basenameWithoutExt, "swift")

env["SWIFT_DRIVER_SWIFT_EXEC"] = "/some/garbage/path/fnord"
let overriddenToolchain = DarwinToolchain(env: env)
let overriddenSwiftPath = try overriddenToolchain.getToolPath(.swiftCompiler)
XCTAssertEqual(overriddenSwiftPath, AbsolutePath("/some/garbage/path/fnord"))
env[envVarName] = dummyPath
let overriddenSwiftPath = try DarwinToolchain(env: env).getToolPath(.swiftCompiler)
XCTAssertEqual(overriddenSwiftPath, AbsolutePath(dummyPath))

// GenericUnixToolchain
env.removeValue(forKey: envVarName)
let unixSwiftPath = try GenericUnixToolchain(env: env).getToolPath(.swiftCompiler)
XCTAssertEqual(unixSwiftPath.basenameWithoutExt, "swift")

env[envVarName] = dummyPath
let unixOverriddenSwiftPath = try GenericUnixToolchain(env: env).getToolPath(.swiftCompiler)
XCTAssertEqual(unixOverriddenSwiftPath, AbsolutePath(dummyPath))
}
}
17 changes: 16 additions & 1 deletion Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ final class SwiftDriverTests: XCTestCase {
XCTAssertFalse(cmd.contains(.flag("-dylib")))
XCTAssertFalse(cmd.contains(.flag("-shared")))
}

do {
// linux target
var driver = try Driver(args: commonArgs + ["-emit-library", "-target", "x86_64-unknown-linux"])
Expand Down Expand Up @@ -893,6 +893,21 @@ final class SwiftDriverTests: XCTestCase {
assertString(swiftVersion, contains: "Swift version ")
#endif
}

func testToolchainClangPath() {
// TODO: remove this conditional check once DarwinToolchain does not requires xcrun to look for clang.
var toolchain: Toolchain
#if os(macOS)
toolchain = DarwinToolchain(env: ProcessEnv.vars)
#else
toolchain = GenericUnixToolchain(env: ProcessEnv.vars)
#endif

XCTAssertEqual(
try? toolchain.getToolPath(.swiftCompiler).parentDirectory,
try? toolchain.getToolPath(.clang).parentDirectory
)
}
}

func assertString(
Expand Down
17 changes: 17 additions & 0 deletions Tests/SwiftDriverTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ extension PrefixTrieTests {
]
}

extension StringAdditionsTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__StringAdditionsTests = [
("testBasicIdentifiers", testBasicIdentifiers),
("testSwiftKeywordsAsIdentifiers", testSwiftKeywordsAsIdentifiers),
("testUnicodeCharacters", testUnicodeCharacters),
]
}

extension SwiftDriverTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
Expand All @@ -70,16 +81,21 @@ extension SwiftDriverTests {
("testMergeModulesOnly", testMergeModulesOnly),
("testModuleNameFallbacks", testModuleNameFallbacks),
("testModuleSettings", testModuleSettings),
("testMultithreading", testMultithreading),
("testMultithreadingDiagnostics", testMultithreadingDiagnostics),
("testOutputFileMapLoading", testOutputFileMapLoading),
("testOutputFileMapStoring", testOutputFileMapStoring),
("testParseErrors", testParseErrors),
("testParsing", testParsing),
("testPrimaryOutputKinds", testPrimaryOutputKinds),
("testPrimaryOutputKindsDiagnostics", testPrimaryOutputKindsDiagnostics),
("testRegressions", testRegressions),
("testResponseFileExpansion", testResponseFileExpansion),
("testResponseFileTokenization", testResponseFileTokenization),
("testSanitizerArgs", testSanitizerArgs),
("testStandardCompileJobs", testStandardCompileJobs),
("testTargetTriple", testTargetTriple),
("testToolchainClangPath", testToolchainClangPath),
("testToolchainUtilities", testToolchainUtilities),
]
}
Expand Down Expand Up @@ -109,6 +125,7 @@ public func __allTests() -> [XCTestCaseEntry] {
testCase(JobExecutorTests.__allTests__JobExecutorTests),
testCase(ParsableMessageTests.__allTests__ParsableMessageTests),
testCase(PrefixTrieTests.__allTests__PrefixTrieTests),
testCase(StringAdditionsTests.__allTests__StringAdditionsTests),
testCase(SwiftDriverTests.__allTests__SwiftDriverTests),
testCase(TripleTests.__allTests__TripleTests),
]
Expand Down