Skip to content

Improved error messages for when swiftc, clang, or sysroot can't be found #588

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 3 commits into from
Aug 29, 2016
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
4 changes: 2 additions & 2 deletions Sources/Build/Command.compile(ClangModule).swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ struct ClangModuleBuildMetadata {
}

extension Command {
static func compile(clangModule module: ClangModule, externalModules: Set<Module>, configuration conf: Configuration, prefix: AbsolutePath, CC: String, otherArgs: [String]) throws -> [Command] {
static func compile(clangModule module: ClangModule, externalModules: Set<Module>, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], compilerExec: AbsolutePath) throws -> [Command] {

let buildMeta = ClangModuleBuildMetadata(module: module, prefix: prefix, otherArgs: otherArgs)

Expand All @@ -138,7 +138,7 @@ extension Command {
let clang = ClangTool(desc: "Compile \(module.name) \(path.filename.asString)",
inputs: buildMeta.inputs + [path.source.asString],
outputs: [path.object.asString],
args: [CC] + args,
args: [compilerExec.asString] + args,
deps: path.deps.asString)

let command = Command(node: path.object.asString, tool: clang)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Build/Command.compile(SwiftModule).swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import PackageLoading
import Utility

extension Command {
static func compile(swiftModule module: SwiftModule, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], SWIFT_EXEC: String) throws -> Command {
static func compile(swiftModule module: SwiftModule, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], compilerExec: AbsolutePath) throws -> Command {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like we should clean up this area to just pass the toolchain around...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I wanted to keep this diff minimal while still doing the basic work of cleaning up the error message, but it make sense to do it as a separate diff in the same PR. I'll do that.

let otherArgs = otherArgs + module.XccFlags(prefix) + (try module.pkgConfigSwiftcArgs()) + module.moduleCacheArgs(prefix: prefix)
var args = ["-j\(SwiftcTool.numThreads)", "-D", "SWIFT_PACKAGE"]

Expand All @@ -29,7 +29,7 @@ extension Command {
args += ["-F", try platformFrameworksPath().asString]
#endif

let tool = SwiftcTool(module: module, prefix: prefix, otherArgs: args + otherArgs, executable: SWIFT_EXEC, conf: conf)
let tool = SwiftcTool(module: module, prefix: prefix, otherArgs: args + otherArgs, executable: compilerExec.asString, conf: conf)
return Command(node: module.targetName, tool: tool)
}
}
4 changes: 2 additions & 2 deletions Sources/Build/Command.link(ClangModule).swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import PackageLoading
import Utility

extension Command {
static func linkClangModule(_ product: Product, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], CC: String) throws -> Command {
static func linkClangModule(_ product: Product, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], linkerExec: AbsolutePath) throws -> Command {
precondition(product.containsOnlyClangModules)

let clangModules = product.modules.flatMap { $0 as? ClangModule }
Expand Down Expand Up @@ -54,7 +54,7 @@ extension Command {
let shell = ShellTool(description: "Linking \(product.name)",
inputs: objects.map{ $0.asString } + inputs,
outputs: [productPath.asString, product.targetName],
args: [CC] + args)
args: [linkerExec.asString] + args)

return Command(node: product.targetName, tool: shell)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Build/Command.link(SwiftModule).swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ import Utility
//FIXME messy :/

extension Command {
static func linkSwiftModule(_ product: Product, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], SWIFT_EXEC: String) throws -> Command {
static func linkSwiftModule(_ product: Product, configuration conf: Configuration, prefix: AbsolutePath, otherArgs: [String], linkerExec: AbsolutePath) throws -> Command {

// Get the unique set of all input modules.
//
// FIXME: This needs to handle C language targets.
let buildables = OrderedSet(product.modules.flatMap{ [$0] + $0.recursiveDependencies }.flatMap{ $0 as? SwiftModule }).contents

var objects = buildables.flatMap { SwiftcTool(module: $0, prefix: prefix, otherArgs: [], executable: SWIFT_EXEC, conf: conf).objects }
var objects = buildables.flatMap { SwiftcTool(module: $0, prefix: prefix, otherArgs: [], executable: linkerExec.asString, conf: conf).objects }

let outpath = prefix.appending(product.outname)

var args: [String]
switch product.type {
case .Library(.Dynamic), .Executable, .Test:
args = [SWIFT_EXEC] + otherArgs
args = [linkerExec.asString] + otherArgs

if conf == .debug {
args += ["-g"]
Expand Down
11 changes: 4 additions & 7 deletions Sources/Build/describe().swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@ public func describe(_ prefix: AbsolutePath, _ conf: Configuration, _ graph: Pac
try makeDirectories(prefix)
let swiftcArgs = flags.cCompilerFlags.flatMap{ ["-Xcc", $0] } + flags.swiftCompilerFlags + verbosity.ccArgs

let SWIFT_EXEC = toolchain.SWIFT_EXEC
let CC = getenv("CC") ?? "clang"

var commands = [Command]()
var targets = Targets()

for module in graph.modules {
switch module {
case let module as SwiftModule:
let compile = try Command.compile(swiftModule: module, configuration: conf, prefix: prefix, otherArgs: swiftcArgs + toolchain.platformArgsSwiftc, SWIFT_EXEC: SWIFT_EXEC)
let compile = try Command.compile(swiftModule: module, configuration: conf, prefix: prefix, otherArgs: swiftcArgs + toolchain.swiftPlatformArgs, compilerExec: toolchain.swiftCompiler)
commands.append(compile)
targets.append([compile], for: module)

Expand All @@ -51,7 +48,7 @@ public func describe(_ prefix: AbsolutePath, _ conf: Configuration, _ graph: Pac
if module.isTest { continue }
#endif
// FIXME: Find a way to eliminate `externalModules` from here.
let compile = try Command.compile(clangModule: module, externalModules: graph.externalModules, configuration: conf, prefix: prefix, CC: CC, otherArgs: flags.cCompilerFlags + toolchain.platformArgsClang)
let compile = try Command.compile(clangModule: module, externalModules: graph.externalModules, configuration: conf, prefix: prefix, otherArgs: flags.cCompilerFlags + toolchain.clangPlatformArgs, compilerExec: toolchain.clangCompiler)
commands += compile
targets.append(compile, for: module)

Expand All @@ -74,9 +71,9 @@ public func describe(_ prefix: AbsolutePath, _ conf: Configuration, _ graph: Pac
#endif
let command: Command
if product.containsOnlyClangModules {
command = try Command.linkClangModule(product, configuration: conf, prefix: prefix, otherArgs: Xld, CC: CC)
command = try Command.linkClangModule(product, configuration: conf, prefix: prefix, otherArgs: Xld, linkerExec: toolchain.clangCompiler)
} else {
command = try Command.linkSwiftModule(product, configuration: conf, prefix: prefix, otherArgs: Xld + swiftcArgs + toolchain.platformArgsSwiftc + rpathArgs, SWIFT_EXEC: SWIFT_EXEC)
command = try Command.linkSwiftModule(product, configuration: conf, prefix: prefix, otherArgs: Xld + swiftcArgs + toolchain.swiftPlatformArgs + rpathArgs, linkerExec: toolchain.swiftCompiler)
}

commands.append(command)
Expand Down
19 changes: 14 additions & 5 deletions Sources/Build/misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ import func POSIX.getenv
import func POSIX.popen

public protocol Toolchain {
var platformArgsClang: [String] { get }
var platformArgsSwiftc: [String] { get }
var sysroot: String? { get }
var SWIFT_EXEC: String { get }
var clang: String { get }
/// Path of the `swiftc` compiler.
var swiftCompiler: AbsolutePath { get }

/// Platform-specific arguments for Swift compiler.
var swiftPlatformArgs: [String] { get }

/// Path of the `clang` compiler.
var clangCompiler: AbsolutePath { get }

/// Platform-specific arguments for Clang compiler.
var clangPlatformArgs: [String] { get }

/// Path of the default SDK (a.k.a. "sysroot"), if any.
var defaultSDK: AbsolutePath? { get }
}

extension AbsolutePath {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Commands/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import enum PackageLoading.ManifestParseError

public enum Error: Swift.Error {
case noManifestFound
case invalidToolchain
case invalidToolchain(problem: String)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realize that having a named property here is inconsistent with the other enum values; I will add names to them in a separate diff but wanted to keep this one small. The reason for adding names will become more clear when we add a solution property as well.

case buildYAMLNotFound(String)
case repositoryHasChanges(String)
}
Expand All @@ -30,8 +30,8 @@ extension Error: FixableError {
switch self {
case .noManifestFound:
return "no \(Manifest.filename) file found"
case .invalidToolchain:
return "invalid inferred toolchain"
case .invalidToolchain(let problem):
return "invalid inferred toolchain: \(problem)"
case .buildYAMLNotFound(let value):
return "no build YAML found: \(value)"
case .repositoryHasChanges(let value):
Expand Down
101 changes: 74 additions & 27 deletions Sources/Commands/UserToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,94 @@ import protocol Build.Toolchain

#if os(macOS)
private let whichClangArgs = ["xcrun", "--find", "clang"]
private let whichDefaultSDKArgs = ["xcrun", "--sdk", "macosx", "--show-sdk-path"]
#else
private let whichClangArgs = ["which", "clang"]
#endif

struct UserToolchain: Toolchain {
let SWIFT_EXEC: String
let clang: String
let sysroot: String?
/// Path of the `swiftc` compiler.
let swiftCompiler: AbsolutePath

/// Path of the `clang` compiler.
let clangCompiler: AbsolutePath

/// Path of the default SDK (a.k.a. "sysroot"), if any.
let defaultSDK: AbsolutePath?

#if os(macOS)
var platformArgsClang: [String] {
return ["-arch", "x86_64", "-mmacosx-version-min=10.10", "-isysroot", sysroot!]
var clangPlatformArgs: [String] {
return ["-arch", "x86_64", "-mmacosx-version-min=10.10", "-isysroot", defaultSDK!.asString]
}

var platformArgsSwiftc: [String] {
return ["-target", "x86_64-apple-macosx10.10", "-sdk", sysroot!]
var swiftPlatformArgs: [String] {
return ["-target", "x86_64-apple-macosx10.10", "-sdk", defaultSDK!.asString]
}
#else
let platformArgsClang: [String] = []
let platformArgsSwiftc: [String] = []
let clangPlatformArgs: [String] = []
let swiftPlatformArgs: [String] = []
#endif

init() throws {
do {
SWIFT_EXEC = getenv("SWIFT_EXEC")
// use the swiftc installed alongside ourselves
?? AbsolutePath(CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory.appending(component: "swiftc").asString

clang = try getenv("CC") ?? POSIX.popen(whichClangArgs).chomp()

#if os(macOS)
sysroot = try getenv("SYSROOT") ?? POSIX.popen(["xcrun", "--sdk", "macosx", "--show-sdk-path"]).chomp()
#else
sysroot = nil
#endif

guard !SWIFT_EXEC.isEmpty && !clang.isEmpty && (sysroot == nil || !sysroot!.isEmpty) else {
throw Error.invalidToolchain
// Find the Swift compiler, looking first in the environment.
if let value = getenv("SWIFT_EXEC"), !value.isEmpty {
// We have a value, but it could be an absolute or a relative path.
swiftCompiler = AbsolutePath(value, relativeTo: currentWorkingDirectory)
}
else {
// No value in env, so look for `swiftc` alongside our own binary.
swiftCompiler = AbsolutePath(CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory.appending(component: "swiftc")
}

// Check that it's valid in the file system.
// FIXME: We should also check that it resolves to an executable file
// (it could be a symlink to such as file).
guard localFileSystem.exists(swiftCompiler) else {
throw Error.invalidToolchain(problem: "could not find `swiftc` at expected path \(swiftCompiler.asString)")
}

// Find the Clang compiler, looking first in the environment.
if let value = getenv("CC"), !value.isEmpty {
// We have a value, but it could be an absolute or a relative path.
clangCompiler = AbsolutePath(value, relativeTo: currentWorkingDirectory)
}
else {
// No value in env, so search for `clang`.
guard let foundPath = try? POSIX.popen(whichClangArgs).chomp(), !foundPath.isEmpty else {
throw Error.invalidToolchain(problem: "could not find `clang`")
}
clangCompiler = AbsolutePath(foundPath, relativeTo: currentWorkingDirectory)
}

// Check that it's valid in the file system.
// FIXME: We should also check that it resolves to an executable file
// (it could be a symlink to such as file).
guard localFileSystem.exists(clangCompiler) else {
throw Error.invalidToolchain(problem: "could not find `clang` at expected path \(clangCompiler.asString)")
}

// Find the default SDK (on macOS only).
#if os(macOS)
if let value = getenv("SYSROOT"), !value.isEmpty {
// We have a value, but it could be an absolute or a relative path.
defaultSDK = AbsolutePath(value, relativeTo: currentWorkingDirectory)
}
else {
// No value in env, so search for it.
guard let foundPath = try? POSIX.popen(whichDefaultSDKArgs).chomp(), !foundPath.isEmpty else {
throw Error.invalidToolchain(problem: "could not find default SDK")
}
defaultSDK = AbsolutePath(foundPath, relativeTo: currentWorkingDirectory)
}

// If we have an SDK, we check that it's valid in the file system.
if let sdk = defaultSDK {
// FIXME: We should probably also check that it is a directory, etc.
guard localFileSystem.exists(sdk) else {
throw Error.invalidToolchain(problem: "could not find default SDK at expected path \(sdk.asString)")
}
} catch POSIX.Error.exitStatus {
throw Error.invalidToolchain
}
#else
defaultSDK = nil
#endif
}
}
10 changes: 5 additions & 5 deletions Tests/BuildTests/DescribeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ final class DescribeTests: XCTestCase {
let dummyPackage = Package(manifest: Manifest(path: AbsolutePath("/"), url: "/", package: PackageDescription.Package(name: "Foo"), products: [], version: nil), path: AbsolutePath("/"), modules: [], testModules: [], products: [])

struct InvalidToolchain: Toolchain {
var platformArgsClang: [String] { fatalError() }
var platformArgsSwiftc: [String] { fatalError() }
var sysroot: String? { fatalError() }
var SWIFT_EXEC: String { fatalError() }
var clang: String { fatalError() }
var swiftCompiler: AbsolutePath { fatalError() }
var clangCompiler: AbsolutePath { fatalError() }
var defaultSDK: AbsolutePath? { fatalError() }
var swiftPlatformArgs: [String] { fatalError() }
var clangPlatformArgs: [String] { fatalError() }
}

func testDescribingNoModulesThrows() {
Expand Down