Skip to content

Commit 889b7f6

Browse files
committed
cross compilation support (configurable destinations)
1 parent dc07093 commit 889b7f6

24 files changed

+321
-172
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -510,11 +510,7 @@ public class BuildPlan {
510510
// Note: This will come from build settings in future.
511511
for target in dependencies.staticTargets {
512512
if case let target as ClangTarget = target.underlyingTarget, target.containsCppFiles {
513-
#if os(macOS)
514-
buildProduct.additionalFlags += ["-lc++"]
515-
#else
516-
buildProduct.additionalFlags += ["-lstdc++"]
517-
#endif
513+
buildProduct.additionalFlags += self.buildParameters.toolchain.destination.extraCPPFlags
518514
break
519515
}
520516
}

Sources/Build/Destination.swift

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import Foundation
2+
import Basic
3+
import PackageModel
4+
5+
enum HostDestinationError: Swift.Error {
6+
/// Couldn't find the Xcode installation.
7+
case invalidInstallation(problem: String)
8+
}
9+
10+
extension HostDestinationError: CustomStringConvertible {
11+
var description: String {
12+
switch self {
13+
case .invalidInstallation(let problem):
14+
return problem
15+
}
16+
}
17+
}
18+
19+
enum DestinationFileError: Swift.Error {
20+
/// Wrong version.
21+
case destinationFileVersionMismatch(problem: String)
22+
}
23+
24+
extension DestinationFileError: CustomStringConvertible {
25+
var description: String {
26+
switch self {
27+
case .destinationFileVersionMismatch(let problem):
28+
return problem
29+
}
30+
}
31+
}
32+
33+
public struct Destination {
34+
public let target: String
35+
36+
public let sdk: AbsolutePath
37+
38+
public let toolchain: AbsolutePath
39+
40+
public let dynamicLibraryExtension: String
41+
42+
public let extraCCFlags: [String]
43+
44+
public let extraSwiftCFlags: [String]
45+
46+
public let extraCPPFlags: [String]
47+
48+
public static func hostDestination() throws -> Destination {
49+
#if os(macOS)
50+
let sdkPath = try Process.checkNonZeroExit(args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
51+
guard !sdkPath.isEmpty else {
52+
throw HostDestinationError.invalidInstallation(problem: "could not find default SDK")
53+
}
54+
55+
let platformPath = try Process.checkNonZeroExit(args: "xcrun", "--sdk", sdkPath, "--show-sdk-platform-path").chomp()
56+
guard !platformPath.isEmpty else {
57+
throw HostDestinationError.invalidInstallation(problem: "could not find SDK platform path for SDK \(sdkPath)")
58+
}
59+
60+
let sdkPlatformFrameworksPath = AbsolutePath(platformPath).appending(components: "Developer", "Library", "Frameworks")
61+
62+
let devDirPath = try Process.checkNonZeroExit(args: "xcode-select", "-p").chomp()
63+
let sysFWPath = AbsolutePath(sdkPath).appending(component: "System")
64+
.appending(component: "Library")
65+
.appending(component: "Frameworks").asString
66+
let tcPath = AbsolutePath(devDirPath).appending(component: "Toolchains")
67+
.appending(component: "XcodeDefault.xctoolchain")
68+
return Destination(target: "x86_64-apple-macosx10.10",
69+
sdk: AbsolutePath(sdkPath),
70+
toolchain: tcPath,
71+
dynamicLibraryExtension: "dylib",
72+
extraCCFlags: ["-F", sdkPlatformFrameworksPath.asString],
73+
extraSwiftCFlags: ["-F", sysFWPath,
74+
"-F", sdkPlatformFrameworksPath.asString,
75+
"-target", "x86_64-apple-macosx10.10",
76+
"-sdk", sdkPath],
77+
extraCPPFlags: ["-lc++"])
78+
#elseif os(Linux)
79+
return Destination(target: "linux-unknown-x86_64",
80+
sdk: AbsolutePath("/"),
81+
toolchain: AbsolutePath(CommandLine.arguments[0],
82+
relativeTo: currentWorkingDirectory)
83+
.parentDirectory.parentDirectory.parentDirectory,
84+
dynamicLibraryExtension: "so",
85+
extraCCFlags: ["-fPIC"],
86+
extraSwiftCFlags: [],
87+
extraCPPFlags: ["-lstdc++"])
88+
#else
89+
fatalError("unsupported OS, sorry")
90+
#endif
91+
}
92+
93+
public static func loadFromDescription(path: AbsolutePath,
94+
fileSystem: FileSystem = localFileSystem) throws -> Destination {
95+
let json = try JSON(bytes: try fileSystem.readFileContents(path))
96+
return try Destination.init(json: json)
97+
}
98+
99+
public func outputFile(name: String, type: PackageModel.ProductType) -> RelativePath {
100+
switch type {
101+
case .executable:
102+
return RelativePath(name)
103+
case .library(.static):
104+
return RelativePath("lib\(name).a")
105+
case .library(.dynamic):
106+
return RelativePath("lib\(name).\(self.dynamicLibraryExtension)")
107+
case .library(.automatic):
108+
return RelativePath("please fix me, this file name shouldn't be used")
109+
case .test:
110+
let base = "\(name).xctest"
111+
#if os(macOS)
112+
return RelativePath("\(base)/Contents/MacOS/\(name)")
113+
#else
114+
return RelativePath(base)
115+
#endif
116+
}
117+
}
118+
119+
#if os(macOS)
120+
public func sdkPlatformFrameworkPath() -> AbsolutePath? {
121+
if let platformPath = try? Process.checkNonZeroExit(args: "xcrun",
122+
"--sdk", self.sdk.asString,
123+
"--show-sdk-platform-path").chomp()
124+
, !platformPath.isEmpty {
125+
return AbsolutePath(platformPath).appending(components: "Developer", "Library", "Frameworks")
126+
} else {
127+
return nil
128+
}
129+
}
130+
#endif
131+
}
132+
133+
extension Destination: JSONMappable {
134+
public init(json: JSON) throws {
135+
if !((json.get("version")).map ({ $0 == "1" }) ?? false) {
136+
throw DestinationFileError.destinationFileVersionMismatch(problem: "expected version 1")
137+
}
138+
self.init(target: try json.get("target"),
139+
sdk: AbsolutePath(try json.get("sdk")),
140+
toolchain: AbsolutePath(try json.get("toolchain")),
141+
dynamicLibraryExtension: try json.get("dynamic-library-extension"),
142+
extraCCFlags: try json.get("extra-cc-flags"),
143+
extraSwiftCFlags: try json.get("extra-swiftc-flags"),
144+
extraCPPFlags: try json.get("extra-cpp-flags"))
145+
}
146+
}

Sources/Build/Toolchain.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public protocol Toolchain {
2424
/// Platform-specific arguments for Clang compiler.
2525
var clangPlatformArgs: [String] { get }
2626

27-
/// Path of the default SDK (a.k.a. "sysroot"), if any.
28-
var defaultSDK: AbsolutePath? { get }
27+
/// The compilation destination.
28+
var destination: Destination { get }
2929
}
3030

3131
extension AbsolutePath {

Sources/Commands/Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,8 @@ public class ToolOptions {
3333
/// Disables sandboxing when executing subprocesses.
3434
public var shouldDisableSandbox = false
3535

36+
/// Path to the comilation destination describing JSON file.
37+
public var destinationDescriptionPath: AbsolutePath?
38+
3639
public required init() {}
3740
}

Sources/Commands/SwiftTestTool.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import class Foundation.ProcessInfo
1313
import Basic
1414
import Build
1515
import Utility
16+
import PackageModel
1617

1718
import func POSIX.exit
1819

@@ -219,9 +220,12 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
219220
var env = ProcessInfo.processInfo.environment
220221
// Add the sdk platform path if we have it. If this is not present, we
221222
// might always end up failing.
222-
if let sdkPlatformFrameworksPath = try getToolchain().sdkPlatformFrameworksPath {
223-
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworksPath.asString
223+
224+
let dest = try Destination.hostDestination()
225+
if let sdkPlatformFrameworkPath = dest.sdkPlatformFrameworkPath()?.asString {
226+
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworkPath
224227
}
228+
225229
try Process.checkNonZeroExit(arguments: args, environment: env)
226230
// Read the temporary file's content.
227231
let data = try fopen(tempFile.path).readFileContents()

Sources/Commands/SwiftTool.swift

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ public class SwiftTool<Options: ToolOptions> {
143143
option: parser.add(option: "--version", kind: Bool.self),
144144
to: { $0.shouldPrintVersion = $1 })
145145

146+
binder.bind(
147+
option: parser.add(option: "--destination", kind: PathArgument.self),
148+
to: { $0.destinationDescriptionPath = $1.path })
149+
146150
// FIXME: We need to allow -vv type options for this.
147151
binder.bind(
148152
option: parser.add(option: "--verbose", shortName: "-v", kind: Bool.self,
@@ -218,6 +222,7 @@ public class SwiftTool<Options: ToolOptions> {
218222
if let workspace = _workspace {
219223
return workspace
220224
}
225+
let destination = try self._destination.dematerialize()
221226
let delegate = ToolWorkspaceDelegate()
222227
let rootPackage = try getPackageRoot()
223228
let provider = GitRepositoryProvider(processSet: processSet)
@@ -229,7 +234,8 @@ public class SwiftTool<Options: ToolOptions> {
229234
toolsVersionLoader: ToolsVersionLoader(),
230235
delegate: delegate,
231236
repositoryProvider: provider,
232-
isResolverPrefetchingEnabled: options.shouldEnableResolverPrefetching
237+
isResolverPrefetchingEnabled: options.shouldEnableResolverPrefetching,
238+
destination: destination
233239
)
234240
_workspace = workspace
235241
return workspace
@@ -339,27 +345,20 @@ public class SwiftTool<Options: ToolOptions> {
339345
}
340346
}
341347

348+
private lazy var _destination: Result<Destination, AnyError> = {
349+
let destination: Destination
350+
if let destinationDescPath = self.options.destinationDescriptionPath {
351+
return Result(anyError: { try Destination.loadFromDescription(path: destinationDescPath) })
352+
} else {
353+
return Result(anyError: Destination.hostDestination)
354+
}
355+
}()
356+
342357
/// Lazily compute the toolchain.
343358
private lazy var _toolchain: Result<UserToolchain, AnyError> = {
344-
345-
#if Xcode
346-
// For Xcode, set bin directory to the build directory containing the fake
347-
// toolchain created during bootstraping. This is obviously not production ready
348-
// and only exists as a development utility right now.
349-
//
350-
// This also means that we should have bootstrapped with the same Swift toolchain
351-
// we're using inside Xcode otherwise we will not be able to load the runtime libraries.
352-
//
353-
// FIXME: We may want to allow overriding this using an env variable but that
354-
// doesn't seem urgent or extremely useful as of now.
355-
let binDir = AbsolutePath(#file).parentDirectory
356-
.parentDirectory.parentDirectory.appending(components: ".build", "debug")
357-
#else
358-
let binDir = AbsolutePath(
359-
CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory
360-
#endif
361-
362-
return Result(anyError: { try UserToolchain(binDir) })
359+
return self._destination.flatMap { destination in
360+
Result(anyError: { try UserToolchain(destination: destination) })
361+
}
363362
}()
364363

365364
private lazy var _manifestLoader: Result<ManifestLoader, AnyError> = {
@@ -421,6 +420,8 @@ private func sandboxProfile(allowedDirectories: [AbsolutePath]) -> String {
421420
stream <<< " (regex #\"^\(directory.asString)/org\\.llvm\\.clang.*\")" <<< "\n"
422421
// For archive tool.
423422
stream <<< " (regex #\"^\(directory.asString)/ar.*\")" <<< "\n"
423+
// For autolink files.
424+
stream <<< " (regex #\"^\(directory.asString)/.*\\.swift-[0-9a-f]+\\.autolink\")" <<< "\n"
424425
}
425426
for directory in allowedDirectories {
426427
stream <<< " (subpath \"\(directory.asString)\")" <<< "\n"

Sources/Commands/UserToolchain.swift

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import POSIX
1212

1313
import Basic
14+
import Build
1415
import PackageLoading
1516
import protocol Build.Toolchain
17+
import PackageModel
1618
import Utility
1719

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

37-
/// Path of the default SDK (a.k.a. "sysroot"), if any.
38-
public let defaultSDK: AbsolutePath?
39-
40-
#if os(macOS)
41-
/// Path to the sdk platform framework path.
42-
public let sdkPlatformFrameworksPath: AbsolutePath?
43-
44-
public var clangPlatformArgs: [String] {
45-
var args = ["-arch", "x86_64", "-mmacosx-version-min=10.10", "-isysroot", defaultSDK!.asString]
46-
if let sdkPlatformFrameworksPath = sdkPlatformFrameworksPath {
47-
args += ["-F", sdkPlatformFrameworksPath.asString]
48-
}
49-
return args
39+
private static func generateClangPlatformArgs(destination: Destination) -> [String] {
40+
return ["-target", destination.target, "--sysroot", destination.sdk.asString] + destination.extraCCFlags
5041
}
51-
public var swiftPlatformArgs: [String] {
52-
var args = ["-target", "x86_64-apple-macosx10.10", "-sdk", defaultSDK!.asString]
53-
if let sdkPlatformFrameworksPath = sdkPlatformFrameworksPath {
54-
args += ["-F", sdkPlatformFrameworksPath.asString]
55-
}
56-
return args
42+
43+
private static func generateSwiftPlatformArgs(destination: Destination) -> [String] {
44+
return ["-target", destination.target, "-sdk", destination.sdk.asString] + destination.extraSwiftCFlags
5745
}
58-
#else
59-
public let clangPlatformArgs: [String] = ["-fPIC"]
60-
public let swiftPlatformArgs: [String] = []
61-
#endif
6246

63-
public init(_ binDir: AbsolutePath) throws {
47+
public let swiftPlatformArgs: [String]
48+
public let clangPlatformArgs: [String]
49+
public let destination: Destination
50+
51+
public init(destination: Destination) throws {
52+
self.destination = destination
53+
6454
// Get the search paths from PATH.
6555
let envSearchPaths = getEnvSearchPaths(
6656
pathString: getenv("PATH"), currentWorkingDirectory: currentWorkingDirectory)
@@ -71,6 +61,7 @@ public struct UserToolchain: Toolchain, ManifestResourceProvider {
7161
searchPaths: envSearchPaths)
7262
}
7363

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

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

107-
// Find the default SDK (on macOS only).
108-
#if os(macOS)
109-
let sdk: AbsolutePath
110-
111-
if let value = lookupExecutablePath(filename: getenv("SYSROOT")) {
112-
sdk = value
113-
} else {
114-
// No value in env, so search for it.
115-
let foundPath = try Process.checkNonZeroExit(
116-
args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
117-
guard !foundPath.isEmpty else {
118-
throw Error.invalidToolchain(problem: "could not find default SDK")
119-
}
120-
sdk = AbsolutePath(foundPath)
121-
}
122-
123-
// Verify that the sdk exists and is a directory
124-
guard localFileSystem.exists(sdk) && localFileSystem.isDirectory(sdk) else {
125-
throw Error.invalidToolchain(problem: "could not find default SDK at expected path \(sdk.asString)")
126-
}
127-
defaultSDK = sdk
128-
129-
// Try to get the platform path.
130-
let platformPath = try? Process.checkNonZeroExit(
131-
args: "xcrun", "--sdk", "macosx", "--show-sdk-platform-path").chomp()
132-
if let platformPath = platformPath, !platformPath.isEmpty {
133-
sdkPlatformFrameworksPath = AbsolutePath(platformPath)
134-
.appending(components: "Developer", "Library", "Frameworks")
135-
} else {
136-
sdkPlatformFrameworksPath = nil
137-
}
138-
#else
139-
defaultSDK = nil
140-
#endif
98+
self.swiftPlatformArgs = UserToolchain.generateSwiftPlatformArgs(destination: destination)
99+
self.clangPlatformArgs = UserToolchain.generateClangPlatformArgs(destination: destination)
141100
}
142101

143102
}

0 commit comments

Comments
 (0)