Skip to content

Commit

Permalink
cross compilation support (configurable destinations)
Browse files Browse the repository at this point in the history
  • Loading branch information
weissi committed Apr 25, 2017
1 parent 8ce5747 commit 4218b22
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 118 deletions.
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 hostDdynamicLibraryExtension = "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

0 comments on commit 4218b22

Please sign in to comment.