Skip to content

Commit 8d3d1d0

Browse files
Allow the "generic Unix" platforms as cross-compile targets (#749)
This allows Linux, FreeBSD, and OpenBSD to be used with Swift SDKs for cross compilation, not only as part of the toolchain on the same host as the target.
1 parent df32ea1 commit 8d3d1d0

File tree

7 files changed

+408
-96
lines changed

7 files changed

+408
-96
lines changed

Sources/SWBCore/SwiftSDK.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ public struct SwiftSDK: Sendable {
8585
}
8686

8787
/// Find Swift SDKs installed by SwiftPM.
88-
public static func findSDKs(targetTriples: [String], fs: any FSProxy, hostOperatingSystem: OperatingSystem) throws -> [SwiftSDK] {
88+
public static func findSDKs(targetTriples: [String]?, fs: any FSProxy, hostOperatingSystem: OperatingSystem) throws -> [SwiftSDK] {
8989
return try findSDKs(swiftSDKsDirectory: defaultSwiftSDKsDirectory(hostOperatingSystem: hostOperatingSystem), targetTriples: targetTriples, fs: fs)
9090
}
9191

92-
private static func findSDKs(swiftSDKsDirectory: Path, targetTriples: [String], fs: any FSProxy) throws -> [SwiftSDK] {
92+
private static func findSDKs(swiftSDKsDirectory: Path, targetTriples: [String]?, fs: any FSProxy) throws -> [SwiftSDK] {
9393
var sdks: [SwiftSDK] = []
9494
// Find .artifactbundle in the SDK directory (e.g. ~/Library/org.swift.swiftpm/swift-sdks)
9595
for artifactBundle in try fs.listdir(swiftSDKsDirectory) {
@@ -118,7 +118,7 @@ public struct SwiftSDK: Sendable {
118118
}
119119

120120
/// Find Swift SDKs in an artifact bundle supporting one of the given targets.
121-
private static func findSDKs(artifactBundle: Path, targetTriples: [String], fs: any FSProxy) throws -> [SwiftSDK] {
121+
private static func findSDKs(artifactBundle: Path, targetTriples: [String]?, fs: any FSProxy) throws -> [SwiftSDK] {
122122
// Load info.json from the artifact bundle
123123
let infoPath = artifactBundle.join("info.json")
124124
guard try fs.isFile(infoPath) else { return [] }
@@ -145,7 +145,9 @@ public struct SwiftSDK: Sendable {
145145

146146
guard let sdk = try SwiftSDK(identifier: identifier, version: artifact.version, path: sdkPath, fs: fs) else { continue }
147147
// Filter out SDKs that don't support any of the target triples.
148-
guard targetTriples.contains(where: { sdk.targetTriples[$0] != nil }) else { continue }
148+
if let targetTriples {
149+
guard targetTriples.contains(where: { sdk.targetTriples[$0] != nil }) else { continue }
150+
}
149151
sdks.append(sdk)
150152
}
151153
}

Sources/SWBGenericUnixPlatform/Plugin.swift

Lines changed: 149 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ struct SwiftTargetInfo: Decodable {
4949
let target: TargetInfo
5050
}
5151

52-
extension SwiftTargetInfo.TargetInfo {
53-
var tripleVersion: String? {
54-
triple != unversionedTriple && triple.system.hasPrefix(unversionedTriple.system) ? String(triple.system.dropFirst(unversionedTriple.system.count)).nilIfEmpty : nil
55-
}
56-
}
57-
5852
struct GenericUnixDeveloperDirectoryExtension: DeveloperDirectoryExtension {
5953
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Core.DeveloperPath? {
6054
if hostOperatingSystem == .windows || hostOperatingSystem == .macOS {
@@ -82,13 +76,12 @@ struct GenericUnixPlatformSpecsExtension: SpecificationsExtension {
8276

8377
struct GenericUnixPlatformInfoExtension: PlatformInfoExtension {
8478
func additionalPlatforms(context: any PlatformInfoExtensionAdditionalPlatformsContext) throws -> [(path: Path, data: [String: PropertyListItem])] {
85-
let operatingSystem = context.hostOperatingSystem
86-
guard operatingSystem.createFallbackSystemToolchain else {
87-
return []
88-
}
89-
90-
return try [
91-
(.root, [
79+
return try OperatingSystem.createFallbackSystemToolchains.compactMap { operatingSystem in
80+
// Only create platforms if the host OS allows a fallback toolchain, or we're cross compiling.
81+
guard operatingSystem.createFallbackSystemToolchain || operatingSystem != context.hostOperatingSystem else {
82+
return nil
83+
}
84+
return try (.root, [
9285
"Type": .plString("Platform"),
9386
"Name": .plString(operatingSystem.xcodePlatformName),
9487
"Identifier": .plString(operatingSystem.xcodePlatformName),
@@ -97,78 +90,156 @@ struct GenericUnixPlatformInfoExtension: PlatformInfoExtension {
9790
"FamilyIdentifier": .plString(operatingSystem.xcodePlatformName),
9891
"IsDeploymentPlatform": .plString("YES"),
9992
])
100-
]
93+
}
10194
}
10295
}
10396

10497
struct GenericUnixSDKRegistryExtension: SDKRegistryExtension {
10598
let plugin: GenericUnixPlugin
10699

107100
func additionalSDKs(context: any SDKRegistryExtensionAdditionalSDKsContext) async throws -> [(path: Path, platform: SWBCore.Platform?, data: [String: PropertyListItem])] {
108-
let operatingSystem = context.hostOperatingSystem
109-
guard operatingSystem.createFallbackSystemToolchain, let platform = try context.platformRegistry.lookup(name: operatingSystem.xcodePlatformName), let swift = plugin.swiftExecutablePath(fs: context.fs) else {
110-
return []
111-
}
101+
return try await OperatingSystem.createFallbackSystemToolchains.asyncMap { operatingSystem in
102+
// Only create SDKs if the host OS allows a fallback toolchain, or we're cross compiling.
103+
guard operatingSystem.createFallbackSystemToolchain || operatingSystem != context.hostOperatingSystem else {
104+
return nil
105+
}
112106

113-
let defaultProperties: [String: PropertyListItem]
114-
switch operatingSystem {
115-
case .linux, .freebsd:
116-
defaultProperties = [
117-
// Workaround to avoid `-dependency_info`.
118-
"LD_DEPENDENCY_INFO_FILE": .plString(""),
119-
120-
"GENERATE_TEXT_BASED_STUBS": "NO",
121-
"GENERATE_INTERMEDIATE_TEXT_BASED_STUBS": "NO",
122-
123-
"CHOWN": "/usr/bin/chown",
124-
"AR": "llvm-ar",
125-
]
126-
default:
127-
defaultProperties = [:]
128-
}
107+
// Don't create any SDKs for the platform if the platform itself isn't registered.
108+
guard let platform = try context.platformRegistry.lookup(name: operatingSystem.xcodePlatformName) else {
109+
return nil
110+
}
129111

130-
let tripleEnvironment: String
131-
switch operatingSystem {
132-
case .linux:
133-
tripleEnvironment = "gnu"
134-
default:
135-
tripleEnvironment = ""
136-
}
112+
var defaultProperties: [String: PropertyListItem]
113+
switch operatingSystem {
114+
case .linux, .freebsd:
115+
defaultProperties = [
116+
// Workaround to avoid `-dependency_info`.
117+
"LD_DEPENDENCY_INFO_FILE": .plString(""),
137118

138-
let swiftTargetInfo = try await plugin.swiftTargetInfo(swiftExecutablePath: swift)
119+
"GENERATE_TEXT_BASED_STUBS": "NO",
120+
"GENERATE_INTERMEDIATE_TEXT_BASED_STUBS": "NO",
139121

140-
let deploymentTargetSettings: [String: PropertyListItem]
141-
if operatingSystem == .freebsd {
142-
guard let tripleVersion = swiftTargetInfo.target.tripleVersion else {
143-
throw StubError.error("Unknown FreeBSD triple version")
122+
"CHOWN": "/usr/bin/chown",
123+
"AR": "llvm-ar",
124+
]
125+
default:
126+
defaultProperties = [:]
144127
}
145-
deploymentTargetSettings = [
146-
"DeploymentTargetSettingName": .plString("FREEBSD_DEPLOYMENT_TARGET"),
147-
"DefaultDeploymentTarget": .plString(tripleVersion),
148-
"MinimumDeploymentTarget": .plString(tripleVersion),
149-
"MaximumDeploymentTarget": .plString(tripleVersion),
150-
]
151-
} else {
152-
deploymentTargetSettings = [:]
153-
}
154128

155-
return try [(.root, platform, [
156-
"Type": .plString("SDK"),
157-
"Version": .plString(Version(ProcessInfo.processInfo.operatingSystemVersion).zeroTrimmed.description),
158-
"CanonicalName": .plString(operatingSystem.xcodePlatformName),
159-
"IsBaseSDK": .plBool(true),
160-
"DefaultProperties": .plDict([
161-
"PLATFORM_NAME": .plString(operatingSystem.xcodePlatformName),
162-
].merging(defaultProperties, uniquingKeysWith: { _, new in new })),
163-
"SupportedTargets": .plDict([
164-
operatingSystem.xcodePlatformName: .plDict([
165-
"Archs": .plArray([.plString(Architecture.hostStringValue ?? "unknown")]),
166-
"LLVMTargetTripleEnvironment": .plString(tripleEnvironment),
167-
"LLVMTargetTripleSys": .plString(operatingSystem.xcodePlatformName),
168-
"LLVMTargetTripleVendor": .plString("unknown"),
169-
].merging(deploymentTargetSettings, uniquingKeysWith: { _, new in new }))
170-
]),
171-
])]
129+
if operatingSystem == .freebsd || operatingSystem != context.hostOperatingSystem {
130+
// FreeBSD is always LLVM-based, and if we're cross-compiling, use lld
131+
defaultProperties["ALTERNATE_LINKER"] = "lld"
132+
}
133+
134+
let tripleEnvironment: String
135+
switch operatingSystem {
136+
case .linux:
137+
tripleEnvironment = "gnu"
138+
default:
139+
tripleEnvironment = ""
140+
}
141+
142+
let swiftSDK: SwiftSDK?
143+
let sysroot: Path
144+
let architectures: [String]
145+
let tripleVersion: String?
146+
let customProperties: [String: PropertyListItem]
147+
if operatingSystem == context.hostOperatingSystem {
148+
swiftSDK = nil
149+
sysroot = .root
150+
architectures = [Architecture.hostStringValue ?? "unknown"]
151+
tripleVersion = nil
152+
customProperties = [:]
153+
} else {
154+
do {
155+
let swiftSDKs = try SwiftSDK.findSDKs(
156+
targetTriples: nil,
157+
fs: context.fs,
158+
hostOperatingSystem: context.hostOperatingSystem
159+
).filter { sdk in
160+
try sdk.targetTriples.keys.map {
161+
try LLVMTriple($0)
162+
}.contains {
163+
switch operatingSystem {
164+
case .linux:
165+
$0.system == "linux" && $0.environment?.hasPrefix("gnu") == true
166+
case .freebsd:
167+
$0.system == "freebsd"
168+
case .openbsd:
169+
$0.system == "openbsd"
170+
default:
171+
throw StubError.error("Unhandled operating system: \(operatingSystem)")
172+
}
173+
}
174+
}
175+
// FIXME: Do something better than just skipping the platform if more than one SDK matches
176+
swiftSDK = swiftSDKs.only
177+
guard let swiftSDK else {
178+
return nil
179+
}
180+
sysroot = swiftSDK.path
181+
architectures = try swiftSDK.targetTriples.keys.map { try LLVMTriple($0).arch }.sorted()
182+
tripleVersion = try Set(swiftSDK.targetTriples.keys.compactMap { try LLVMTriple($0).systemVersion }).only?.description
183+
customProperties = try Dictionary(uniqueKeysWithValues: swiftSDK.targetTriples.map { targetTriple in
184+
try ("__SYSROOT_\(LLVMTriple(targetTriple.key).arch)", .plString(swiftSDK.path.join(targetTriple.value.sdkRootPath).str))
185+
}).merging([
186+
"SYSROOT": "$(__SYSROOT_$(CURRENT_ARCH))",
187+
188+
// ld.lld: error: -r and --export-dynamic (-rdynamic) may not be used together
189+
"LD_EXPORT_GLOBAL_SYMBOLS": "YES",
190+
], uniquingKeysWith: { _, new in new })
191+
} catch {
192+
// FIXME: Handle errors?
193+
return nil
194+
}
195+
}
196+
197+
let deploymentTargetSettings: [String: PropertyListItem]
198+
if operatingSystem == .freebsd {
199+
let realTripleVersion: String
200+
if context.hostOperatingSystem == operatingSystem {
201+
guard let swift = plugin.swiftExecutablePath(fs: context.fs) else {
202+
throw StubError.error("Cannot locate swift executable path for determining the FreeBSD triple version")
203+
}
204+
let swiftTargetInfo = try await plugin.swiftTargetInfo(swiftExecutablePath: swift)
205+
guard let foundTripleVersion = try swiftTargetInfo.target.triple.version?.description else {
206+
throw StubError.error("Unknown FreeBSD triple version")
207+
}
208+
realTripleVersion = foundTripleVersion
209+
} else if let tripleVersion {
210+
realTripleVersion = tripleVersion
211+
} else {
212+
return nil // couldn't compute triple version for FreeBSD
213+
}
214+
deploymentTargetSettings = [
215+
"DeploymentTargetSettingName": .plString("FREEBSD_DEPLOYMENT_TARGET"),
216+
"DefaultDeploymentTarget": .plString(realTripleVersion),
217+
"MinimumDeploymentTarget": .plString(realTripleVersion),
218+
"MaximumDeploymentTarget": .plString(realTripleVersion),
219+
]
220+
} else {
221+
deploymentTargetSettings = [:]
222+
}
223+
224+
return try (sysroot, platform, [
225+
"Type": .plString("SDK"),
226+
"Version": .plString(Version(ProcessInfo.processInfo.operatingSystemVersion).zeroTrimmed.description),
227+
"CanonicalName": .plString(operatingSystem.xcodePlatformName),
228+
"IsBaseSDK": .plBool(true),
229+
"DefaultProperties": .plDict([
230+
"PLATFORM_NAME": .plString(operatingSystem.xcodePlatformName),
231+
].merging(defaultProperties, uniquingKeysWith: { _, new in new })),
232+
"CustomProperties": .plDict(customProperties),
233+
"SupportedTargets": .plDict([
234+
operatingSystem.xcodePlatformName: .plDict([
235+
"Archs": .plArray(architectures.map { .plString($0) }),
236+
"LLVMTargetTripleEnvironment": .plString(tripleEnvironment),
237+
"LLVMTargetTripleSys": .plString(operatingSystem.xcodePlatformName),
238+
"LLVMTargetTripleVendor": .plString("unknown"),
239+
].merging(deploymentTargetSettings, uniquingKeysWith: { _, new in new }))
240+
]),
241+
])
242+
}.compactMap { $0 }
172243
}
173244
}
174245

@@ -218,8 +289,12 @@ struct GenericUnixToolchainRegistryExtension: ToolchainRegistryExtension {
218289
}
219290

220291
extension OperatingSystem {
292+
static var createFallbackSystemToolchains: [OperatingSystem] {
293+
[.linux, .freebsd, .openbsd]
294+
}
295+
221296
/// Whether the Core is allowed to create a fallback toolchain, SDK, and platform for this operating system in cases where no others have been provided.
222-
var createFallbackSystemToolchain: Bool {
223-
return self == .linux || self == .freebsd || self == .openbsd
297+
fileprivate var createFallbackSystemToolchain: Bool {
298+
return Self.createFallbackSystemToolchains.contains(self)
224299
}
225300
}

Sources/SWBGenericUnixPlatform/Specs/UnixCompile.xcspec

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
{
3939
Name = SDKROOT;
4040
Type = Path;
41+
CommandLineArgs = ();
42+
},
43+
{
44+
Name = SYSROOT;
45+
DefaultValue = "$(SDKROOT)";
46+
Type = Path;
4147
CommandLineFlag = "--sysroot";
4248
},
4349
{
@@ -91,6 +97,12 @@
9197
{
9298
Name = SDKROOT;
9399
Type = Path;
100+
CommandLineArgs = ();
101+
},
102+
{
103+
Name = SYSROOT;
104+
DefaultValue = "$(SDKROOT)";
105+
Type = Path;
94106
CommandLineFlag = "--sysroot";
95107
},
96108
);
@@ -106,6 +118,12 @@
106118
{
107119
Name = SDKROOT;
108120
Type = Path;
121+
CommandLineArgs = ();
122+
},
123+
{
124+
Name = SYSROOT;
125+
DefaultValue = "$(SDKROOT)";
126+
Type = Path;
109127
CommandLineFlag = "--sysroot";
110128
},
111129
);

Sources/SWBGenericUnixPlatform/Specs/UnixLd.xcspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@
5353
{
5454
Name = CLANG_SDKROOT_LINKER_INPUT;
5555
Type = Path;
56-
DefaultValue = "$(SDKROOT)";
56+
DefaultValue = "$(SYSROOT:default=$(SDKROOT))";
5757
Condition = "$(LINKER_DRIVER) == clang";
5858
CommandLineFlag = "--sysroot";
5959
IsInputDependency = Yes;
6060
},
6161
{
6262
Name = SWIFTC_SDKROOT_LINKER_INPUT;
6363
Type = Path;
64-
DefaultValue = "$(SDKROOT)";
64+
DefaultValue = "$(SYSROOT:default=$(SDKROOT))";
6565
Condition = "$(LINKER_DRIVER) == swiftc";
6666
CommandLineFlag = "-sysroot";
6767
IsInputDependency = Yes;

0 commit comments

Comments
 (0)