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 20, 2017
1 parent dc07093 commit 455b06e
Show file tree
Hide file tree
Showing 24 changed files with 320 additions and 171 deletions.
6 changes: 1 addition & 5 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -510,11 +510,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.destination.extraCPPFlags
break
}
}
Expand Down
146 changes: 146 additions & 0 deletions Sources/Build/Destination.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import Foundation
import Basic
import PackageModel

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

extension HostDestinationError: CustomStringConvertible {
var description: String {
switch self {
case .invalidInstallation(let problem):
return problem
}
}
}

enum DestinationFileError: Swift.Error {
/// Wrong version.
case destinationFileVersionMismatch(problem: String)
}

extension DestinationFileError: CustomStringConvertible {
var description: String {
switch self {
case .destinationFileVersionMismatch(let problem):
return problem
}
}
}

public struct Destination {
public let target: String

public let sdk: AbsolutePath

public let toolchain: AbsolutePath

public let dynamicLibraryExtension: String

public let extraCCFlags: [String]

public let extraSwiftCFlags: [String]

public let extraCPPFlags: [String]

public static func hostDestination() throws -> Destination {
#if os(macOS)
let sdkPath = try Process.checkNonZeroExit(args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
guard !sdkPath.isEmpty else {
throw HostDestinationError.invalidInstallation(problem: "could not find default SDK")
}

let platformPath = try Process.checkNonZeroExit(args: "xcrun", "--sdk", sdkPath, "--show-sdk-platform-path").chomp()
guard !platformPath.isEmpty else {
throw HostDestinationError.invalidInstallation(problem: "could not find SDK platform path for SDK \(sdkPath)")
}

let sdkPlatformFrameworksPath = AbsolutePath(platformPath).appending(components: "Developer", "Library", "Frameworks")

let devDirPath = try Process.checkNonZeroExit(args: "xcode-select", "-p").chomp()
let sysFWPath = AbsolutePath(sdkPath).appending(component: "System")
.appending(component: "Library")
.appending(component: "Frameworks").asString
let tcPath = AbsolutePath(devDirPath).appending(component: "Toolchains")
.appending(component: "XcodeDefault.xctoolchain")
return Destination(target: "x86_64-apple-macosx10.10",
sdk: AbsolutePath(sdkPath),
toolchain: tcPath,
dynamicLibraryExtension: "dylib",
extraCCFlags: ["-F", sdkPlatformFrameworksPath.asString],
extraSwiftCFlags: ["-F", sysFWPath,
"-F", sdkPlatformFrameworksPath.asString,
"-target", "x86_64-apple-macosx10.10",
"-sdk", sdkPath],
extraCPPFlags: ["-lc++"])
#elseif os(Linux)
return Destination(target: "linux-unknown-x86_64",
sdk: AbsolutePath("/"),
toolchain: AbsolutePath(CommandLine.arguments[0],
relativeTo: currentWorkingDirectory)
.parentDirectory.parentDirectory.parentDirectory,
dynamicLibraryExtension: "so",
extraCCFlags: ["-fPIC"],
extraSwiftCFlags: [],
extraCPPFlags: ["-lstdc++"])
#else
fatalError("unsupported OS, sorry")
#endif
}

public static func loadFromDescription(path: AbsolutePath,
fileSystem: FileSystem = localFileSystem) throws -> Destination {
let json = try JSON(bytes: try fileSystem.readFileContents(path))
return try Destination.init(json: json)
}

public func outputFile(name: String, type: PackageModel.ProductType) -> RelativePath {
switch type {
case .executable:
return RelativePath(name)
case .library(.static):
return RelativePath("lib\(name).a")
case .library(.dynamic):
return RelativePath("lib\(name).\(self.dynamicLibraryExtension)")
case .library(.automatic):
return RelativePath("please fix me, this file name shouldn't be used")
case .test:
let base = "\(name).xctest"
#if os(macOS)
return RelativePath("\(base)/Contents/MacOS/\(name)")
#else
return RelativePath(base)
#endif
}
}

#if os(macOS)
public func sdkPlatformFrameworkPath() -> AbsolutePath? {
if let platformPath = try? Process.checkNonZeroExit(args: "xcrun",
"--sdk", self.sdk.asString,
"--show-sdk-platform-path").chomp()
, !platformPath.isEmpty {
return AbsolutePath(platformPath).appending(components: "Developer", "Library", "Frameworks")
} else {
return nil
}
}
#endif
}

extension Destination: JSONMappable {
public init(json: JSON) throws {
if !((json.get("version")).map ({ $0 == "1" }) ?? false) {
throw DestinationFileError.destinationFileVersionMismatch(problem: "expected version 1")
}
self.init(target: try json.get("target"),
sdk: AbsolutePath(try json.get("sdk")),
toolchain: AbsolutePath(try json.get("toolchain")),
dynamicLibraryExtension: try json.get("dynamic-library-extension"),
extraCCFlags: try json.get("extra-cc-flags"),
extraSwiftCFlags: try json.get("extra-swiftc-flags"),
extraCPPFlags: try json.get("extra-cpp-flags"))
}
}
4 changes: 2 additions & 2 deletions Sources/Build/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public protocol Toolchain {
/// 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 }
/// The compilation destination.
var destination: Destination { get }
}

extension AbsolutePath {
Expand Down
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 comilation destination describing JSON file.
public var destinationDescriptionPath: AbsolutePath?

public required init() {}
}
8 changes: 6 additions & 2 deletions Sources/Commands/SwiftTestTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import class Foundation.ProcessInfo
import Basic
import Build
import Utility
import PackageModel

import func POSIX.exit

Expand Down Expand Up @@ -219,9 +220,12 @@ 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 {
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworksPath.asString

let dest = try Destination.hostDestination()
if let sdkPlatformFrameworkPath = dest.sdkPlatformFrameworkPath()?.asString {
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworkPath
}

try Process.checkNonZeroExit(arguments: args, environment: env)
// Read the temporary file's content.
let data = try fopen(tempFile.path).readFileContents()
Expand Down
41 changes: 21 additions & 20 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.destinationDescriptionPath = $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 @@ -218,6 +222,7 @@ public class SwiftTool<Options: ToolOptions> {
if let workspace = _workspace {
return workspace
}
let destination = try self._destination.dematerialize()
let delegate = ToolWorkspaceDelegate()
let rootPackage = try getPackageRoot()
let provider = GitRepositoryProvider(processSet: processSet)
Expand All @@ -229,7 +234,8 @@ public class SwiftTool<Options: ToolOptions> {
toolsVersionLoader: ToolsVersionLoader(),
delegate: delegate,
repositoryProvider: provider,
isResolverPrefetchingEnabled: options.shouldEnableResolverPrefetching
isResolverPrefetchingEnabled: options.shouldEnableResolverPrefetching,
destination: destination
)
_workspace = workspace
return workspace
Expand Down Expand Up @@ -339,27 +345,20 @@ public class SwiftTool<Options: ToolOptions> {
}
}

private lazy var _destination: Result<Destination, AnyError> = {
let destination: Destination
if let destinationDescPath = self.options.destinationDescriptionPath {
return Result(anyError: { try Destination.loadFromDescription(path: destinationDescPath) })
} else {
return Result(anyError: Destination.hostDestination)
}
}()

/// 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

return Result(anyError: { try UserToolchain(binDir) })
return self._destination.flatMap { destination in
Result(anyError: { try UserToolchain(destination: destination) })
}
}()

private lazy var _manifestLoader: Result<ManifestLoader, AnyError> = {
Expand Down Expand Up @@ -421,6 +420,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
75 changes: 17 additions & 58 deletions Sources/Commands/UserToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import POSIX

import Basic
import Build
import PackageLoading
import protocol Build.Toolchain
import PackageModel
import Utility

#if os(macOS)
Expand All @@ -34,33 +36,21 @@ public struct UserToolchain: Toolchain, ManifestResourceProvider {
/// Path to SwiftPM library directory containing runtime libraries.
public let libDir: AbsolutePath

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

#if os(macOS)
/// Path to the sdk platform framework path.
public let sdkPlatformFrameworksPath: AbsolutePath?

public var clangPlatformArgs: [String] {
var args = ["-arch", "x86_64", "-mmacosx-version-min=10.10", "-isysroot", defaultSDK!.asString]
if let sdkPlatformFrameworksPath = sdkPlatformFrameworksPath {
args += ["-F", sdkPlatformFrameworksPath.asString]
}
return args
private static func generateClangPlatformArgs(destination: Destination) -> [String] {
return ["-target", destination.target, "--sysroot", destination.sdk.asString] + destination.extraCCFlags
}
public var swiftPlatformArgs: [String] {
var args = ["-target", "x86_64-apple-macosx10.10", "-sdk", defaultSDK!.asString]
if let sdkPlatformFrameworksPath = sdkPlatformFrameworksPath {
args += ["-F", sdkPlatformFrameworksPath.asString]
}
return args

private static func generateSwiftPlatformArgs(destination: Destination) -> [String] {
return ["-target", destination.target, "-sdk", destination.sdk.asString] + destination.extraSwiftCFlags
}
#else
public let clangPlatformArgs: [String] = ["-fPIC"]
public let swiftPlatformArgs: [String] = []
#endif

public init(_ binDir: AbsolutePath) throws {
public let swiftPlatformArgs: [String]
public let clangPlatformArgs: [String]
public let destination: Destination

public init(destination: Destination) throws {
self.destination = destination

// Get the search paths from PATH.
let envSearchPaths = getEnvSearchPaths(
pathString: getenv("PATH"), currentWorkingDirectory: currentWorkingDirectory)
Expand All @@ -71,6 +61,7 @@ public struct UserToolchain: Toolchain, ManifestResourceProvider {
searchPaths: envSearchPaths)
}

let binDir = destination.toolchain.appending(component: "usr").appending(component: "bin")
libDir = binDir.parentDirectory.appending(components: "lib", "swift", "pm")

// First look in env and then in bin dir.
Expand Down Expand Up @@ -104,40 +95,8 @@ public struct UserToolchain: Toolchain, ManifestResourceProvider {
throw Error.invalidToolchain(problem: "could not find `clang` at expected path \(clangCompiler.asString)")
}

// Find the default SDK (on macOS only).
#if os(macOS)
let sdk: AbsolutePath

if let value = lookupExecutablePath(filename: getenv("SYSROOT")) {
sdk = value
} else {
// No value in env, so search for it.
let foundPath = try Process.checkNonZeroExit(
args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
guard !foundPath.isEmpty else {
throw Error.invalidToolchain(problem: "could not find default SDK")
}
sdk = AbsolutePath(foundPath)
}

// Verify that the sdk exists and is a directory
guard localFileSystem.exists(sdk) && localFileSystem.isDirectory(sdk) else {
throw Error.invalidToolchain(problem: "could not find default SDK at expected path \(sdk.asString)")
}
defaultSDK = sdk

// Try to get the platform path.
let platformPath = try? Process.checkNonZeroExit(
args: "xcrun", "--sdk", "macosx", "--show-sdk-platform-path").chomp()
if let platformPath = platformPath, !platformPath.isEmpty {
sdkPlatformFrameworksPath = AbsolutePath(platformPath)
.appending(components: "Developer", "Library", "Frameworks")
} else {
sdkPlatformFrameworksPath = nil
}
#else
defaultSDK = nil
#endif
self.swiftPlatformArgs = UserToolchain.generateSwiftPlatformArgs(destination: destination)
self.clangPlatformArgs = UserToolchain.generateClangPlatformArgs(destination: destination)
}

}
Loading

0 comments on commit 455b06e

Please sign in to comment.