Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0756787
Swift SDK for WASM using the run destination
cmcgee1024 Dec 4, 2025
222f639
Restore the emit executable argument in Ld.xcspec
cmcgee1024 Dec 9, 2025
0211f9a
Override the rpath argument to the linker in the webassembly ld xcspe…
cmcgee1024 Dec 9, 2025
7a136b5
Move the platform registerSDK call to its original position
cmcgee1024 Dec 9, 2025
ae50208
Undo use of sdk.canonicalName instead of the provided canonical name
cmcgee1024 Dec 9, 2025
07993ad
Fix compile error in tests
cmcgee1024 Dec 9, 2025
d6bce25
Formatting
cmcgee1024 Dec 9, 2025
87af09f
Base the OTHER_LDFLAGS on the extra swift compiler settings
cmcgee1024 Dec 10, 2025
66cfd6c
Fix typo
cmcgee1024 Dec 10, 2025
6bdaeef
Introduce a new synthesizedSDK method for SDK registries that special…
cmcgee1024 Dec 10, 2025
6b5f572
Formatting and remove active run destination qualifier
cmcgee1024 Dec 10, 2025
4237bf4
Make explicit build target in run destination with both Apple and Swi…
cmcgee1024 Dec 12, 2025
ba56abd
Move the workaround for the UnixLd removal of the -sdk link argument …
cmcgee1024 Dec 12, 2025
2d02364
Rework the SWBBuildTarget API into a struct instead of an enum
cmcgee1024 Dec 16, 2025
afa0d52
Add a new platformName capability to PlatformInfoExtension, using tha…
cmcgee1024 Dec 16, 2025
95e4f38
Refactor name of appleSDK to toolchainSDK
cmcgee1024 Dec 17, 2025
e7cc4ae
Introduce compatibility with encoding/decoding of the build targets
cmcgee1024 Dec 17, 2025
f26b36b
Code review feedback
cmcgee1024 Dec 18, 2025
b9b7022
Add a test case with an example WASM SDK to test run destination hand…
cmcgee1024 Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion Sources/SWBCore/SDKRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send

// Construct the SDK and add it to the registry.
let sdk = SDK(canonicalName, canonicalNameComponents: try? parseSDKName(canonicalName), aliases, cohortPlatforms, displayName, path, version, productBuildVersion, defaultSettings, overrideSettings, variants, defaultDeploymentTarget, defaultVariant, (headerSearchPaths, frameworkSearchPaths, librarySearchPaths), directoryMacros.elements, isBaseSDK, fallbackSettingConditionValues, toolchains, versionMap, maximumDeploymentTarget)

if let duplicate = sdksByCanonicalName[canonicalName] {
delegate.error(path, "SDK '\(canonicalName)' already registered from \(duplicate.path.str)")
return nil
Expand Down Expand Up @@ -1111,8 +1112,103 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send
}
}

// Xcode has special logic so that if there's no match here, and we'e *not* looking for a suffixed SDK, but we have an suffixed SDK which would otherwise match, then we use that. c.f. <rdar://problem/11414721> But we haven't needed that logic yet in Swift Build, so maybe we never will.
// Let's check the active run destination to see if there's an SDK path that we should be using
if matchedSDK == nil,
let sdkManifestPath = activeRunDestination?.sdkManiftestPath,
let triple = activeRunDestination?.triple,
case let llvmTriple = try LLVMTriple(triple) {

// TODO choose the platform based on the triple, and fallback to a default one in the default case
guard let platform = delegate.platformRegistry?.lookup(name: "webassembly") else {
return nil
}

let host = hostOperatingSystem

// Don't allow re-registering the same SDK
if let existing = sdksByCanonicalName[sdkManifestPath] {
return existing
}

if let swiftSDK = try SwiftSDK(identifier: sdkManifestPath, version: "1.0.0", path: Path(sdkManifestPath), fs: localFS) {
let defaultProperties: [String: PropertyListItem] = [
"SDK_STAT_CACHE_ENABLE": "NO",

"GENERATE_TEXT_BASED_STUBS": "NO",
"GENERATE_INTERMEDIATE_TEXT_BASED_STUBS": "NO",

// TODO check how this impacts operation on Windows
"CHOWN": "/usr/bin/chown",

// TODO are these going to be appropriate for all kinds of SDK's?
// SwiftSDK _could_ have tool entries for these, so use them if they are available
"LIBTOOL": .plString(host.imageFormat.executableName(basename: "llvm-lib")),
"AR": .plString(host.imageFormat.executableName(basename: "llvm-ar")),
]

for (sdkTriple, tripleProperties) in swiftSDK.targetTriples {
guard triple == sdkTriple else {
continue
}

let toolsets = try tripleProperties.loadToolsets(sdk: swiftSDK, fs: localFS)

let sysroot = swiftSDK.path.join(tripleProperties.sdkRootPath)

// TODO support dynamic resources path
let swiftResourceDir = swiftSDK.path.join(tripleProperties.swiftStaticResourcesPath)
let clangResourceDir = swiftSDK.path.join(tripleProperties.clangStaticResourcesPath)

let tripleSystem = llvmTriple.system + (llvmTriple.systemVersion?.description ?? "")

// TODO handle tripleProperties.toolSearchPaths

let extraSwiftCompilerSettings = Array(toolsets.map( { $0.swiftCompiler?.extraCLIOptions ?? [] }).flatMap( { $0 }))
let headerSearchPaths: [PropertyListItem] = ["$(inherited)"] + (tripleProperties.includeSearchPaths ?? []).map( { PropertyListItem.plString($0) } )
let librarySearchPaths: [PropertyListItem] = ["$(inherited)"] + (tripleProperties.librarySearchPaths ?? []).map( { PropertyListItem.plString($0) } )

let sdk = registerSDK(sysroot, sysroot, platform, .plDict([
"Type": .plString("SDK"),
"Version": .plString(swiftSDK.version),
"CanonicalName": .plString(swiftSDK.identifier),
"Aliases": [],
"IsBaseSDK": .plBool(true),
"DefaultProperties": .plDict([
"PLATFORM_NAME": .plString(platform.name),
].merging(defaultProperties, uniquingKeysWith: { _, new in new })),
"CustomProperties": .plDict([
"LIBRARY_SEARCH_PATHS": .plArray(librarySearchPaths),
"HEADER_SEARCH_PATHS": .plArray(headerSearchPaths),
"OTHER_SWIFT_FLAGS": .plArray(["$(inherited)"] + extraSwiftCompilerSettings.map( {.plString($0)} )),
"SWIFTC_RESOURCE_DIR": .plString(swiftResourceDir.str), // Resource dir for linking Swift
"SWIFT_RESOURCE_DIR": .plString(swiftResourceDir.str), // Resource dir for compiling Swift
"CLANG_RESOURCE_DIR": .plString(clangResourceDir.str), // Resource dir for linking C/C++/Obj-C
"SDKROOT": .plString(sysroot.str),
"OTHER_LDFLAGS": .plArray(["$(OTHER_SWIFT_FLAGS)"]), // The extra swift compiler settings in JSON are intended to go to the linker driver too
Copy link
Collaborator

@owenv owenv Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to include swift flags here, we should use extraSwiftCompilerSettings instead of $(OTHER_SWIFT_FLAGS), because $(OTHER_SWIFT_FLAGS) may include other project/command line flags which are only intended for the compiler

]),
"SupportedTargets": .plDict([
"webassembly": .plDict([
"Archs": .plArray([.plString(llvmTriple.arch)]),
"LLVMTargetTripleEnvironment": .plString(llvmTriple.environment ?? ""),
"LLVMTargetTripleSys": .plString(tripleSystem),
"LLVMTargetTripleVendor": .plString(llvmTriple.vendor),
])
]),
// TODO: Leave compatible toolchain information in Swift SDKs
// "Toolchains": .plArray([])
]))

// FIXME why do we need to do this to avoid initialization errors on the SDK's default properties table?
if let sdk {
try sdk.loadExtendedInfo(delegate.namespace)

return sdk
}
}
}
}

// Xcode has special logic so that if there's no match here, and we'e *not* looking for a suffixed SDK, but we have an suffixed SDK which would otherwise match, then we use that. c.f. <rdar://problem/11414721> But we haven't needed that logic yet in Swift Build, so maybe we never will.
return matchedSDK?.sdk
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SWBCore/Settings/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3504,7 +3504,7 @@ private class SettingsBuilder: ProjectMatchLookup {
// If the target supports specialization and it already has an SDK, then we need to use that instead of attempting to override the SDK with the run destination information. This is very important in scenarios where the destination is Mac Catalyst, but the target is building for iphoneos. The target will be re-configured for macosx/iosmac.
do {
let scope = createScope(sdkToUse: nil)
let sdk = try sdkRegistry.lookup(scope.evaluate(BuiltinMacros.SDKROOT).str, activeRunDestination: nil)
let sdk = try sdkRegistry.lookup(scope.evaluate(BuiltinMacros.SDKROOT).str, activeRunDestination: parameters.activeRunDestination)
if Settings.targetPlatformSpecializationEnabled(scope: scope) && sdk != nil {
return
}
Expand Down
52 changes: 41 additions & 11 deletions Sources/SWBCore/SwiftSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,53 @@ public struct SwiftSDK: Sendable {
public var sdkRootPath: String
public var swiftResourcesPath: String?
public var swiftStaticResourcesPath: String?
public var clangResourcesPath: String? {
guard let swiftResourcesPath = self.swiftResourcesPath else {
return nil
}

// The clang resource path is conventionally the clang subdirectory of the swift resource path
return Path(swiftResourcesPath).join("clang").str
}
public var clangStaticResourcesPath: String? {
guard let swiftResourcesPath = self.swiftStaticResourcesPath else {
return nil
}

// The clang resource path is conventionally the clang subdirectory of the swift resource path
return Path(swiftResourcesPath).join("clang").str
}
public var includeSearchPaths: [String]?
public var librarySearchPaths: [String]?
public var toolsetPaths: [String]?

public func loadToolsets(sdk: SwiftSDK, fs: any FSProxy) throws -> [Toolset] {
var toolsets: [Toolset] = []

for toolsetPath in self.toolsetPaths ?? [] {
let metadataData = try Data(fs.read(sdk.path.join(toolsetPath)))

let schema = try JSONDecoder().decode(SchemaVersionInfo.self, from: metadataData)
guard schema.schemaVersion == "1.0" else { return [] } // FIXME throw an error

let toolset = try JSONDecoder().decode(Toolset.self, from: metadataData)
toolsets.append(toolset)
}

return toolsets
}
}
struct MetadataV4: Codable {
let targetTriples: [String: TripleProperties]
}

struct Toolset: Codable {
struct ToolProperties: Codable {
var path: String?
var extraCLIOptions: [String]
public struct Toolset: Codable {
public struct Tool: Codable {
public let extraCLIOptions: [String]?
}

var knownTools: [String: ToolProperties] = [:]
var rootPaths: [String] = []
public let rootPath: String
public let swiftCompiler: Tool?
}

/// The identifier of the artifact bundle containing this SDK.
Expand All @@ -55,12 +86,11 @@ public struct SwiftSDK: Sendable {
init?(identifier: String, version: String, path: Path, fs: any FSProxy) throws {
self.identifier = identifier
self.version = version
self.path = path
self.path = path.dirname

let metadataPath = path.join("swift-sdk.json")
guard fs.exists(metadataPath) else { return nil }
guard fs.exists(path) else { return nil }

let metadataData = try Data(fs.read(metadataPath))
let metadataData = try Data(fs.read(path))
let schema = try JSONDecoder().decode(SchemaVersionInfo.self, from: metadataData)
guard schema.schemaVersion == "4.0" else { return nil }

Expand Down Expand Up @@ -118,7 +148,7 @@ public struct SwiftSDK: Sendable {
}

/// Find Swift SDKs in an artifact bundle supporting one of the given targets.
private static func findSDKs(artifactBundle: Path, targetTriples: [String]?, fs: any FSProxy) throws -> [SwiftSDK] {
public static func findSDKs(artifactBundle: Path, targetTriples: [String]?, fs: any FSProxy) throws -> [SwiftSDK] {
// Load info.json from the artifact bundle
let infoPath = artifactBundle.join("info.json")
guard try fs.isFile(infoPath) else { return [] }
Expand Down
6 changes: 5 additions & 1 deletion Sources/SWBProtocol/MessageSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,18 @@ public struct RunDestinationInfo: SerializableCodable, Hashable, Sendable {
public var supportedArchitectures: OrderedSet<String>
public var disableOnlyActiveArch: Bool
public var hostTargetedPlatform: String?
public var sdkManiftestPath: String?
public var triple: String?

public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: OrderedSet<String>, disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) {
public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: OrderedSet<String>, sdkManiftestPath: String? = nil, triple: String? = nil, disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) {
self.platform = platform
self.sdk = sdk
self.sdkVariant = sdkVariant
self.targetArchitecture = targetArchitecture
self.supportedArchitectures = supportedArchitectures
self.disableOnlyActiveArch = disableOnlyActiveArch
self.sdkManiftestPath = sdkManiftestPath
self.triple = triple
self.hostTargetedPlatform = hostTargetedPlatform
}
}
Expand Down
21 changes: 21 additions & 0 deletions Sources/SWBUniversalPlatform/Specs/Ld.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,27 @@
CommandLineFlag = "-sdk";
IsInputDependency = Yes;
},
{
Name = CLANG_RESOURCE_DIR;
Type = Path;
Condition = "$(LINKER_DRIVER) == clang";
CommandLineFlag = "-resource-dir";
IsInputDependency = Yes;
},
{
Name = CLANG_RESOURCE_DIR;
Type = Path;
Condition = "$(LINKER_DRIVER) == swiftc";
CommandLineArgs = ("-Xclang-linker", "-resource-dir", "-Xclang-linker", "$(CLANG_RESOURCE_DIR)");
IsInputDependency = Yes;
},
{
Name = SWIFTC_RESOURCE_DIR;
Type = Path;
Condition = "$(LINKER_DRIVER) == swiftc";
CommandLineArgs = ("-resource-dir", "$(SWIFTC_RESOURCE_DIR)");
IsInputDependency = Yes;
},
{
Name = "LD_OPTIMIZATION_LEVEL";
Type = String;
Expand Down
8 changes: 8 additions & 0 deletions Sources/SWBWebAssemblyPlatform/Specs/WasmLd.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@
DefaultValue = "";
Condition = "NO";
},
{
// Wasm doesn't have search paths
Name = "LD_RUNPATH_SEARCH_PATHS";
//Note: Cannot be of type 'PathList' as value is used with relative '../' paths
Type = StringList;
Condition = "NO";
CommandLineArgs = ();
},
{
Name = GOLD_BUILDID;
Type = Boolean;
Expand Down
6 changes: 6 additions & 0 deletions Sources/SWBWebAssemblyPlatform/Specs/WebAssembly.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@
);
SortNumber = 0;
},
{
Domain = webassembly;
Type = ProductType;
Identifier = org.swift.product-type.common.object;
BasedOn = com.apple.product-type.library.static;
},
)
6 changes: 5 additions & 1 deletion Sources/SwiftBuild/SWBBuildParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public struct SWBRunDestinationInfo: Codable, Sendable {
public var supportedArchitectures: [String]
public var disableOnlyActiveArch: Bool
public var hostTargetedPlatform: String?
public var sdkManifestPath: String?
public var triple: String?

public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool) {
self.platform = platform
Expand All @@ -53,9 +55,11 @@ public struct SWBRunDestinationInfo: Codable, Sendable {
self.disableOnlyActiveArch = disableOnlyActiveArch
}

public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) {
public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], sdkManifestPath: String? = nil, triple: String? = nil, disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) {
self.init(platform: platform, sdk: sdk, sdkVariant: sdkVariant, targetArchitecture: targetArchitecture, supportedArchitectures: supportedArchitectures, disableOnlyActiveArch: disableOnlyActiveArch)
self.hostTargetedPlatform = hostTargetedPlatform
self.sdkManifestPath = sdkManifestPath
self.triple = triple
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftBuild/SWBBuildServiceSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ extension SWBBuildParameters {

fileprivate extension RunDestinationInfo {
init(_ x: SWBRunDestinationInfo) {
self.init(platform: x.platform, sdk: x.sdk, sdkVariant: x.sdkVariant, targetArchitecture: x.targetArchitecture, supportedArchitectures: OrderedSet(x.supportedArchitectures), disableOnlyActiveArch: x.disableOnlyActiveArch, hostTargetedPlatform: x.hostTargetedPlatform)
self.init(platform: x.platform, sdk: x.sdk, sdkVariant: x.sdkVariant, targetArchitecture: x.targetArchitecture, supportedArchitectures: OrderedSet(x.supportedArchitectures), sdkManiftestPath: x.sdkManifestPath, triple: x.triple, disableOnlyActiveArch: x.disableOnlyActiveArch, hostTargetedPlatform: x.hostTargetedPlatform)
}
}

Expand Down