Skip to content

Commit 51c05a6

Browse files
committed
cross compilation support (configurable destinations)
1 parent ca959bd commit 51c05a6

File tree

13 files changed

+280
-118
lines changed

13 files changed

+280
-118
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public final class ClangTargetDescription {
166166
/// Builds up basic compilation arguments for this target.
167167
public func basicArguments() -> [String] {
168168
var args = [String]()
169-
args += buildParameters.toolchain.clangPlatformArgs
169+
args += buildParameters.toolchain.extraCCFlags
170170
args += buildParameters.flags.cCompilerFlags
171171
args += optimizationArguments
172172
// Only enable ARC on macOS.
@@ -261,7 +261,7 @@ public final class SwiftTargetDescription {
261261
public func compileArguments() -> [String] {
262262
var args = [String]()
263263
args += ["-swift-version", String(swiftVersion)]
264-
args += buildParameters.toolchain.swiftPlatformArgs
264+
args += buildParameters.toolchain.extraSwiftCFlags
265265
args += buildParameters.swiftCompilerFlags
266266
args += optimizationArguments
267267
args += ["-j\(SwiftCompilerTool.numThreads)", "-DSWIFT_PACKAGE"]
@@ -310,7 +310,7 @@ public final class ProductBuildDescription {
310310
case .library(.static):
311311
return RelativePath("lib\(name).a")
312312
case .library(.dynamic):
313-
return RelativePath("lib\(name).\(Product.dynamicLibraryExtension)")
313+
return RelativePath("lib\(name).\(self.buildParameters.toolchain.dynamicLibraryExtension)")
314314
case .library(.automatic):
315315
fatalError()
316316
case .test:
@@ -354,7 +354,7 @@ public final class ProductBuildDescription {
354354
/// The arguments to link and create this product.
355355
public func linkArguments() -> [String] {
356356
var args = [buildParameters.toolchain.swiftCompiler.asString]
357-
args += buildParameters.toolchain.swiftPlatformArgs
357+
args += buildParameters.toolchain.extraSwiftCFlags
358358
args += buildParameters.linkerFlags
359359
args += stripInvalidArguments(buildParameters.swiftCompilerFlags)
360360
args += additionalFlags
@@ -533,11 +533,7 @@ public class BuildPlan {
533533
// Note: This will come from build settings in future.
534534
for target in dependencies.staticTargets {
535535
if case let target as ClangTarget = target.underlyingTarget, target.containsCppFiles {
536-
#if os(macOS)
537-
buildProduct.additionalFlags += ["-lc++"]
538-
#else
539-
buildProduct.additionalFlags += ["-lstdc++"]
540-
#endif
536+
buildProduct.additionalFlags += self.buildParameters.toolchain.extraCPPFlags
541537
break
542538
}
543539
}

Sources/Build/Toolchain.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ public protocol Toolchain {
1515
/// Path of the `swiftc` compiler.
1616
var swiftCompiler: AbsolutePath { get }
1717

18-
/// Platform-specific arguments for Swift compiler.
19-
var swiftPlatformArgs: [String] { get }
20-
2118
/// Path of the `clang` compiler.
2219
var clangCompiler: AbsolutePath { get }
2320

24-
/// Platform-specific arguments for Clang compiler.
25-
var clangPlatformArgs: [String] { get }
21+
/// Additional flags to be passed to the C compiler.
22+
var extraCCFlags: [String] { get }
23+
24+
/// Additional flags to be passed to the Swift compiler.
25+
var extraSwiftCFlags: [String] { get }
26+
27+
/// Additional flags to be passed when compiling with C++.
28+
var extraCPPFlags: [String] { get }
2629

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

3134
extension AbsolutePath {

Sources/Commands/Destination.swift

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import Basic
2+
import Utility
3+
import POSIX
4+
5+
enum DestinationError: Swift.Error {
6+
/// Couldn't find the Xcode installation.
7+
case invalidInstallation(String)
8+
9+
/// The schema version is invalid.
10+
case invalidSchemaVersion
11+
}
12+
13+
extension DestinationError: CustomStringConvertible {
14+
var description: String {
15+
switch self {
16+
case .invalidSchemaVersion:
17+
return "unsupported destination file schema version"
18+
case .invalidInstallation(let problem):
19+
return problem
20+
}
21+
}
22+
}
23+
24+
/// The compilation destination, has information about everything that's required for a certain destination.
25+
public struct Destination {
26+
27+
/// The clang/LLVM triple describing the target OS and architecture.
28+
///
29+
/// The triple has the general format <arch><sub>-<vendor>-<sys>-<abi>, where:
30+
/// - arch = x86_64, i386, arm, thumb, mips, etc.
31+
/// - sub = for ex. on ARM: v5, v6m, v7a, v7m, etc.
32+
/// - vendor = pc, apple, nvidia, ibm, etc.
33+
/// - sys = none, linux, win32, darwin, cuda, etc.
34+
/// - abi = eabi, gnu, android, macho, elf, etc.
35+
///
36+
/// for more information see //https://clang.llvm.org/docs/CrossCompilation.html
37+
public let target: String
38+
39+
/// The SDK used to compile for the destination.
40+
public let sdk: AbsolutePath
41+
42+
/// The binDir in the containing the compilers/linker to be used for the compilation.
43+
public let binDir: AbsolutePath
44+
45+
/// The file extension for dynamic libraries (eg. `.so` or `.dylib`)
46+
public let dynamicLibraryExtension: String
47+
48+
/// Additional flags to be passed to the C compiler.
49+
public let extraCCFlags: [String]
50+
51+
/// Additional flags to be passed to the Swift compiler.
52+
public let extraSwiftCFlags: [String]
53+
54+
/// Additional flags to be passed when compiling with C++.
55+
public let extraCPPFlags: [String]
56+
57+
/// Returns the bin directory for the host.
58+
private static func hostBinDir() -> AbsolutePath {
59+
#if Xcode
60+
// For Xcode, set bin directory to the build directory containing the fake
61+
// toolchain created during bootstraping. This is obviously not production ready
62+
// and only exists as a development utility right now.
63+
//
64+
// This also means that we should have bootstrapped with the same Swift toolchain
65+
// we're using inside Xcode otherwise we will not be able to load the runtime libraries.
66+
//
67+
// FIXME: We may want to allow overriding this using an env variable but that
68+
// doesn't seem urgent or extremely useful as of now.
69+
return AbsolutePath(#file).parentDirectory
70+
.parentDirectory.parentDirectory.appending(components: ".build", "debug")
71+
#endif
72+
return AbsolutePath(
73+
CommandLine.arguments[0], relativeTo: currentWorkingDirectory).parentDirectory
74+
}
75+
76+
/// The destination describing the host OS.
77+
public static func hostDestination(_ binDir: AbsolutePath? = nil) throws -> Destination {
78+
// Select the correct binDir.
79+
let binDir = binDir ?? Destination.hostBinDir()
80+
81+
#if os(macOS)
82+
// Get the SDK.
83+
let sdkPath: AbsolutePath
84+
if let value = lookupExecutablePath(filename: getenv("SYSROOT")) {
85+
sdkPath = value
86+
} else {
87+
// No value in env, so search for it.
88+
let sdkPathStr = try Process.checkNonZeroExit(
89+
args: "xcrun", "--sdk", "macosx", "--show-sdk-path").chomp()
90+
guard !sdkPathStr.isEmpty else {
91+
throw DestinationError.invalidInstallation("could not find default SDK")
92+
}
93+
sdkPath = AbsolutePath(sdkPathStr)
94+
}
95+
96+
// Compute common arguments for clang and swift.
97+
// This is currently just frameworks path.
98+
let commonArgs = Destination.sdkPlatformFrameworkPath().map({ ["-F", $0.asString] }) ?? []
99+
100+
return Destination(
101+
target: "x86_64-apple-macosx10.10",
102+
sdk: sdkPath,
103+
binDir: binDir,
104+
dynamicLibraryExtension: "dylib",
105+
extraCCFlags: ["-arch", "x86_64", "-mmacosx-version-min=10.10"] + commonArgs,
106+
extraSwiftCFlags: commonArgs,
107+
extraCPPFlags: ["-lc++"]
108+
)
109+
#else
110+
return Destination(
111+
target: "linux-unknown-x86_64",
112+
sdk: .root,
113+
binDir: binDir,
114+
dynamicLibraryExtension: "so",
115+
extraCCFlags: ["-fPIC"],
116+
extraSwiftCFlags: [],
117+
extraCPPFlags: ["-lstdc++"]
118+
)
119+
#endif
120+
}
121+
122+
/// Returns macosx sdk platform framework path.
123+
public static func sdkPlatformFrameworkPath() -> AbsolutePath? {
124+
if let path = _sdkPlatformFrameworkPath {
125+
return path
126+
}
127+
let platformPath = try? Process.checkNonZeroExit(
128+
args: "xcrun", "--sdk", "macosx", "--show-sdk-platform-path").chomp()
129+
130+
if let platformPath = platformPath, !platformPath.isEmpty {
131+
_sdkPlatformFrameworkPath = AbsolutePath(platformPath).appending(
132+
components: "Developer", "Library", "Frameworks")
133+
}
134+
return _sdkPlatformFrameworkPath
135+
}
136+
/// Cache storage for sdk platform path.
137+
private static var _sdkPlatformFrameworkPath: AbsolutePath? = nil
138+
139+
#if os(macOS)
140+
/// Returns the host's dynamic library extension.
141+
public static let hostDynamicLibraryExtension = "dylib"
142+
#else
143+
/// Returns the host's dynamic library extension.
144+
public static let hostDdynamicLibraryExtension = "so"
145+
#endif
146+
}
147+
148+
public extension Destination {
149+
150+
/// Load a Destination description from a JSON representation from disk.
151+
public init(fromFile path: AbsolutePath, fileSystem: FileSystem = localFileSystem) throws {
152+
let json = try JSON(bytes: fileSystem.readFileContents(path))
153+
try self.init(json: json)
154+
}
155+
}
156+
157+
extension Destination: JSONMappable {
158+
159+
/// The current schema version.
160+
static let schemaVersion = 1
161+
162+
public init(json: JSON) throws {
163+
164+
// Check schema version.
165+
guard try json.get("version") == Destination.schemaVersion else {
166+
throw DestinationError.invalidSchemaVersion
167+
}
168+
169+
try self.init(target: json.get("target"),
170+
sdk: AbsolutePath(json.get("sdk")),
171+
binDir: AbsolutePath(json.get("toolchain-bin-dir")),
172+
dynamicLibraryExtension: json.get("dynamic-library-extension"),
173+
extraCCFlags: json.get("extra-cc-flags"),
174+
extraSwiftCFlags: json.get("extra-swiftc-flags"),
175+
extraCPPFlags: json.get("extra-cpp-flags")
176+
)
177+
}
178+
}

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 compilation destination describing JSON file.
37+
public var customCompileDestination: AbsolutePath?
38+
3639
public required init() {}
3740
}

Sources/Commands/SwiftTestTool.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
219219
var env = ProcessInfo.processInfo.environment
220220
// Add the sdk platform path if we have it. If this is not present, we
221221
// might always end up failing.
222-
if let sdkPlatformFrameworksPath = try getToolchain().sdkPlatformFrameworksPath {
222+
if let sdkPlatformFrameworksPath = Destination.sdkPlatformFrameworkPath() {
223223
env["DYLD_FRAMEWORK_PATH"] = sdkPlatformFrameworksPath.asString
224224
}
225225
try Process.checkNonZeroExit(arguments: args, environment: env)

Sources/Commands/SwiftTool.swift

Lines changed: 26 additions & 23 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.customCompileDestination = $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,
@@ -281,9 +285,9 @@ public class SwiftTool<Options: ToolOptions> {
281285
return graph
282286
}
283287

284-
/// Returns the user toolchain.
288+
/// Returns the user toolchain to compile the actual product.
285289
func getToolchain() throws -> UserToolchain {
286-
return try _toolchain.dematerialize()
290+
return try _destinationToolchain.dematerialize()
287291
}
288292

289293
func getManifestLoader() throws -> ManifestLoader {
@@ -339,33 +343,30 @@ public class SwiftTool<Options: ToolOptions> {
339343
}
340344
}
341345

342-
/// Lazily compute the toolchain.
343-
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
346+
/// Lazily compute the destination toolchain.
347+
private lazy var _destinationToolchain: Result<UserToolchain, AnyError> = {
348+
// Create custom toolchain if present.
349+
if let customDestination = self.options.customCompileDestination {
350+
return Result(anyError: {
351+
try UserToolchain(destination: Destination(fromFile: customDestination))
352+
})
353+
}
354+
// Otherwise use the host toolchain.
355+
return self._hostToolchain
356+
}()
361357

362-
return Result(anyError: { try UserToolchain(binDir) })
358+
/// Lazily compute the host toolchain used to compile the package description.
359+
private lazy var _hostToolchain: Result<UserToolchain, AnyError> = {
360+
return Result(anyError: {
361+
try UserToolchain(destination: Destination.hostDestination())
362+
})
363363
}()
364364

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

0 commit comments

Comments
 (0)