Skip to content

cross compilation support (configurable destinations) #1098

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 1 commit into from
Apr 25, 2017
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
14 changes: 5 additions & 9 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public final class ClangTargetDescription {
/// Builds up basic compilation arguments for this target.
public func basicArguments() -> [String] {
var args = [String]()
args += buildParameters.toolchain.clangPlatformArgs
args += buildParameters.toolchain.extraCCFlags
args += buildParameters.flags.cCompilerFlags
args += optimizationArguments
// Only enable ARC on macOS.
Expand Down Expand Up @@ -261,7 +261,7 @@ public final class SwiftTargetDescription {
public func compileArguments() -> [String] {
var args = [String]()
args += ["-swift-version", String(swiftVersion)]
args += buildParameters.toolchain.swiftPlatformArgs
args += buildParameters.toolchain.extraSwiftCFlags
args += buildParameters.swiftCompilerFlags
args += optimizationArguments
args += ["-j\(SwiftCompilerTool.numThreads)", "-DSWIFT_PACKAGE"]
Expand Down Expand Up @@ -310,7 +310,7 @@ public final class ProductBuildDescription {
case .library(.static):
return RelativePath("lib\(name).a")
case .library(.dynamic):
return RelativePath("lib\(name).\(Product.dynamicLibraryExtension)")
return RelativePath("lib\(name).\(self.buildParameters.toolchain.dynamicLibraryExtension)")
case .library(.automatic):
fatalError()
case .test:
Expand Down Expand Up @@ -354,7 +354,7 @@ public final class ProductBuildDescription {
/// The arguments to link and create this product.
public func linkArguments() -> [String] {
var args = [buildParameters.toolchain.swiftCompiler.asString]
args += buildParameters.toolchain.swiftPlatformArgs
args += buildParameters.toolchain.extraSwiftCFlags
args += buildParameters.linkerFlags
args += stripInvalidArguments(buildParameters.swiftCompilerFlags)
args += additionalFlags
Expand Down Expand Up @@ -533,11 +533,7 @@ public class BuildPlan {
// Note: This will come from build settings in future.
for target in dependencies.staticTargets {
if case let target as ClangTarget = target.underlyingTarget, target.containsCppFiles {
#if os(macOS)
buildProduct.additionalFlags += ["-lc++"]
#else
buildProduct.additionalFlags += ["-lstdc++"]
#endif
buildProduct.additionalFlags += self.buildParameters.toolchain.extraCPPFlags
break
}
}
Expand Down
17 changes: 10 additions & 7 deletions Sources/Build/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ public protocol Toolchain {
/// 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 }
/// Additional flags to be passed to the C compiler.
var extraCCFlags: [String] { get }

/// Additional flags to be passed to the Swift compiler.
var extraSwiftCFlags: [String] { get }

/// Additional flags to be passed when compiling with C++.
var extraCPPFlags: [String] { get }

/// Path of the default SDK (a.k.a. "sysroot"), if any.
var defaultSDK: AbsolutePath? { get }
/// The dynamic library extension, for e.g. dylib, so.
var dynamicLibraryExtension: String { get }
}

extension AbsolutePath {
Expand Down
178 changes: 178 additions & 0 deletions Sources/Commands/Destination.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import Basic
import Utility
import POSIX

enum DestinationError: Swift.Error {
/// Couldn't find the Xcode installation.
case invalidInstallation(String)

/// The schema version is invalid.
case invalidSchemaVersion
}

extension DestinationError: CustomStringConvertible {
var description: String {
switch self {
case .invalidSchemaVersion:
return "unsupported destination file schema version"
case .invalidInstallation(let problem):
return problem
}
}
}

/// The compilation destination, has information about everything that's required for a certain destination.
public struct Destination {

/// The clang/LLVM triple describing the target OS and architecture.
///
/// The triple has the general format <arch><sub>-<vendor>-<sys>-<abi>, where:
/// - arch = x86_64, i386, arm, thumb, mips, etc.
/// - sub = for ex. on ARM: v5, v6m, v7a, v7m, etc.
/// - vendor = pc, apple, nvidia, ibm, etc.
/// - sys = none, linux, win32, darwin, cuda, etc.
/// - abi = eabi, gnu, android, macho, elf, etc.
///
/// for more information see //https://clang.llvm.org/docs/CrossCompilation.html
public let target: String

/// The SDK used to compile for the destination.
public let sdk: AbsolutePath

/// The binDir in the containing the compilers/linker to be used for the compilation.
public let binDir: AbsolutePath

/// The file extension for dynamic libraries (eg. `.so` or `.dylib`)
public let dynamicLibraryExtension: String

/// Additional flags to be passed to the C compiler.
public let extraCCFlags: [String]

/// Additional flags to be passed to the Swift compiler.
public let extraSwiftCFlags: [String]

/// Additional flags to be passed when compiling with C++.
public let extraCPPFlags: [String]

/// Returns the bin directory for the host.
private static func hostBinDir() -> AbsolutePath {
#if Xcode
// For Xcode, set bin directory to the build directory containing the fake
// toolchain created during bootstraping. This is obviously not production ready
// and only exists as a development utility right now.
//
// This also means that we should have bootstrapped with the same Swift toolchain
// we're using inside Xcode otherwise we will not be able to load the runtime libraries.
//
// FIXME: We may want to allow overriding this using an env variable but that
// doesn't seem urgent or extremely useful as of now.
return AbsolutePath(#file).parentDirectory
.parentDirectory.parentDirectory.appending(components: ".build", "debug")
#endif
return AbsolutePath(
CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory
}

/// The destination describing the host OS.
public static func hostDestination(_ binDir: AbsolutePath? = nil) throws -> Destination {
// Select the correct binDir.
let binDir = binDir ?? Destination.hostBinDir()

#if os(macOS)
// Get the SDK.
let sdkPath: AbsolutePath
if let value = lookupExecutablePath(filename: getenv("SYSROOT")) {
sdkPath = value
} else {
// No value in env, so search for it.
let sdkPathStr = try Process.checkNonZeroExit(
args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
guard !sdkPathStr.isEmpty else {
throw DestinationError.invalidInstallation("could not find default SDK")
}
sdkPath = AbsolutePath(sdkPathStr)
}

// Compute common arguments for clang and swift.
// This is currently just frameworks path.
let commonArgs = Destination.sdkPlatformFrameworkPath().map({ ["-F", $0.asString] }) ?? []

return Destination(
target: "x86_64-apple-macosx10.10",
sdk: sdkPath,
binDir: binDir,
dynamicLibraryExtension: "dylib",
extraCCFlags: ["-arch", "x86_64", "-mmacosx-version-min=10.10"] + commonArgs,
extraSwiftCFlags: commonArgs,
extraCPPFlags: ["-lc++"]
)
#else
return Destination(
target: "linux-unknown-x86_64",
sdk: .root,
binDir: binDir,
dynamicLibraryExtension: "so",
extraCCFlags: ["-fPIC"],
extraSwiftCFlags: [],
extraCPPFlags: ["-lstdc++"]
)
#endif
}

/// Returns macosx sdk platform framework path.
public static func sdkPlatformFrameworkPath() -> AbsolutePath? {
if let path = _sdkPlatformFrameworkPath {
return path
}
let platformPath = try? Process.checkNonZeroExit(
args: "xcrun", "--sdk", "macosx", "--show-sdk-platform-path").chomp()

if let platformPath = platformPath, !platformPath.isEmpty {
_sdkPlatformFrameworkPath = AbsolutePath(platformPath).appending(
components: "Developer", "Library", "Frameworks")
}
return _sdkPlatformFrameworkPath
}
/// Cache storage for sdk platform path.
private static var _sdkPlatformFrameworkPath: AbsolutePath? = nil

#if os(macOS)
/// Returns the host's dynamic library extension.
public static let hostDynamicLibraryExtension = "dylib"
#else
/// Returns the host's dynamic library extension.
public static let hostDynamicLibraryExtension = "so"
#endif
}

public extension Destination {

/// Load a Destination description from a JSON representation from disk.
public init(fromFile path: AbsolutePath, fileSystem: FileSystem = localFileSystem) throws {
let json = try JSON(bytes: fileSystem.readFileContents(path))
try self.init(json: json)
}
}

extension Destination: JSONMappable {

/// The current schema version.
static let schemaVersion = 1

public init(json: JSON) throws {

// Check schema version.
guard try json.get("version") == Destination.schemaVersion else {
throw DestinationError.invalidSchemaVersion
}

try self.init(target: json.get("target"),
sdk: AbsolutePath(json.get("sdk")),
binDir: AbsolutePath(json.get("toolchain-bin-dir")),
dynamicLibraryExtension: json.get("dynamic-library-extension"),
extraCCFlags: json.get("extra-cc-flags"),
extraSwiftCFlags: json.get("extra-swiftc-flags"),
extraCPPFlags: json.get("extra-cpp-flags")
)
}
}
3 changes: 3 additions & 0 deletions Sources/Commands/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ public class ToolOptions {
/// Disables sandboxing when executing subprocesses.
public var shouldDisableSandbox = false

/// Path to the compilation destination describing JSON file.
public var customCompileDestination: AbsolutePath?

public required init() {}
}
2 changes: 1 addition & 1 deletion Sources/Commands/SwiftTestTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
var env = ProcessInfo.processInfo.environment
// Add the sdk platform path if we have it. If this is not present, we
// might always end up failing.
if let sdkPlatformFrameworksPath = try getToolchain().sdkPlatformFrameworksPath {
if let sdkPlatformFrameworksPath = Destination.sdkPlatformFrameworkPath() {
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworksPath.asString
}
try Process.checkNonZeroExit(arguments: args, environment: env)
Expand Down
49 changes: 26 additions & 23 deletions Sources/Commands/SwiftTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public class SwiftTool<Options: ToolOptions> {
option: parser.add(option: "--version", kind: Bool.self),
to: { $0.shouldPrintVersion = $1 })

binder.bind(
option: parser.add(option: "--destination", kind: PathArgument.self),
to: { $0.customCompileDestination = $1.path })

// FIXME: We need to allow -vv type options for this.
binder.bind(
option: parser.add(option: "--verbose", shortName: "-v", kind: Bool.self,
Expand Down Expand Up @@ -281,9 +285,9 @@ public class SwiftTool<Options: ToolOptions> {
return graph
}

/// Returns the user toolchain.
/// Returns the user toolchain to compile the actual product.
func getToolchain() throws -> UserToolchain {
return try _toolchain.dematerialize()
return try _destinationToolchain.dematerialize()
}

func getManifestLoader() throws -> ManifestLoader {
Expand Down Expand Up @@ -339,33 +343,30 @@ public class SwiftTool<Options: ToolOptions> {
}
}

/// Lazily compute the toolchain.
private lazy var _toolchain: Result<UserToolchain, AnyError> = {

#if Xcode
// For Xcode, set bin directory to the build directory containing the fake
// toolchain created during bootstraping. This is obviously not production ready
// and only exists as a development utility right now.
//
// This also means that we should have bootstrapped with the same Swift toolchain
// we're using inside Xcode otherwise we will not be able to load the runtime libraries.
//
// FIXME: We may want to allow overriding this using an env variable but that
// doesn't seem urgent or extremely useful as of now.
let binDir = AbsolutePath(#file).parentDirectory
.parentDirectory.parentDirectory.appending(components: ".build", "debug")
#else
let binDir = AbsolutePath(
CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory
#endif
/// Lazily compute the destination toolchain.
private lazy var _destinationToolchain: Result<UserToolchain, AnyError> = {
// Create custom toolchain if present.
if let customDestination = self.options.customCompileDestination {
return Result(anyError: {
try UserToolchain(destination: Destination(fromFile: customDestination))
})
}
// Otherwise use the host toolchain.
return self._hostToolchain
}()

return Result(anyError: { try UserToolchain(binDir) })
/// Lazily compute the host toolchain used to compile the package description.
private lazy var _hostToolchain: Result<UserToolchain, AnyError> = {
return Result(anyError: {
try UserToolchain(destination: Destination.hostDestination())
})
}()

private lazy var _manifestLoader: Result<ManifestLoader, AnyError> = {
return Result(anyError: {
try ManifestLoader(
resources: self.getToolchain().manifestResources,
// Always use the host toolchain's resources for parsing manifest.
resources: self._hostToolchain.dematerialize().manifestResources,
isManifestSandboxEnabled: !self.options.shouldDisableSandbox
)
})
Expand Down Expand Up @@ -421,6 +422,8 @@ private func sandboxProfile(allowedDirectories: [AbsolutePath]) -> String {
stream <<< " (regex #\"^\(directory.asString)/org\\.llvm\\.clang.*\")" <<< "\n"
// For archive tool.
stream <<< " (regex #\"^\(directory.asString)/ar.*\")" <<< "\n"
// For autolink files.
stream <<< " (regex #\"^\(directory.asString)/.*\\.swift-[0-9a-f]+\\.autolink\")" <<< "\n"
}
for directory in allowedDirectories {
stream <<< " (subpath \"\(directory.asString)\")" <<< "\n"
Expand Down
Loading