diff --git a/Sources/SWBCore/BuildRequestContext.swift b/Sources/SWBCore/BuildRequestContext.swift index d971d45c..3c9c7a78 100644 --- a/Sources/SWBCore/BuildRequestContext.swift +++ b/Sources/SWBCore/BuildRequestContext.swift @@ -197,10 +197,11 @@ extension BuildRequestContext { func platformAndSDKVariant(for target: ConfiguredTarget) -> PlatformAndSDKVariant { if hasEnabledIndexBuildArena, let activeRunDestination = target.parameters.activeRunDestination, - let platform = workspaceContext.core.platformRegistry.lookup(name: activeRunDestination.platform) { + case let .toolchainSDK(platform: platform, _, sdkVariant: sdkVariant) = activeRunDestination.buildTarget, + let platform = workspaceContext.core.platformRegistry.lookup(name: platform) { // Configured targets include their platform in parameters, we can use it directly and avoid the expense of `getCachedSettings()` calls. // If in future `ConfiguredTarget` carries along an instance of its Settings, we can avoid this check and go back to using `Settings` without the cost of `getCachedSettings`. - return PlatformAndSDKVariant(platform: platform, sdkVariant: activeRunDestination.sdkVariant) + return PlatformAndSDKVariant(platform: platform, sdkVariant: sdkVariant) } else { let settings = getCachedSettings(target.parameters, target: target.target) return PlatformAndSDKVariant(platform: settings.platform, sdkVariant: settings.sdkVariant?.name) @@ -249,9 +250,14 @@ extension BuildRequestContext { guard let destination = runDestination else { return selectWithoutRunDestination() } - if matchesPlatform(lhsPlatform, platformName: destination.platform, sdkVariant: destination.sdkVariant) { return lhs } - if matchesPlatform(rhsPlatform, platformName: destination.platform, sdkVariant: destination.sdkVariant) { return rhs } - guard let destinationPlatform = workspaceContext.core.platformRegistry.lookup(name: destination.platform) else { + + guard case let .toolchainSDK(platform: platform, _, sdkVariant: sdkVariant) = destination.buildTarget else { + return selectWithoutRunDestination() + } + + if matchesPlatform(lhsPlatform, platformName: platform, sdkVariant: sdkVariant) { return lhs } + if matchesPlatform(rhsPlatform, platformName: platform, sdkVariant: sdkVariant) { return rhs } + guard let destinationPlatform = workspaceContext.core.platformRegistry.lookup(name: platform) else { return selectWithoutRunDestination() } if lhsPlatform.platform?.familyName != rhsPlatform.platform?.familyName { diff --git a/Sources/SWBCore/ConfiguredTarget.swift b/Sources/SWBCore/ConfiguredTarget.swift index 5ab5c0c5..7424a40b 100644 --- a/Sources/SWBCore/ConfiguredTarget.swift +++ b/Sources/SWBCore/ConfiguredTarget.swift @@ -65,9 +65,11 @@ public final class ConfiguredTarget: Hashable, CustomStringConvertible, Serializ if !parameters.isEmpty { components.append(("parameters", parameters.joined(separator: "-"))) } - if specializeGuidForActiveRunDestination, let runDestination = self.parameters.activeRunDestination { - var runDestString = runDestination.platform - if let sdkVariant = runDestination.sdkVariant { + if specializeGuidForActiveRunDestination, + let runDestination = self.parameters.activeRunDestination, + case let .toolchainSDK(platform: platform, _, sdkVariant: sdkVariant) = runDestination.buildTarget { + var runDestString = platform + if let sdkVariant = sdkVariant { runDestString += "+\(sdkVariant)" } components.append(("runDestination", runDestString)) @@ -106,8 +108,17 @@ public final class ConfiguredTarget: Hashable, CustomStringConvertible, Serializ return nil } } + if specializeGuidForActiveRunDestination { - let discriminator = self.parameters.activeRunDestination.map{ "\($0.platform)-\($0.sdkVariant ?? "")" } ?? "" + let discriminator: String + switch self.parameters.activeRunDestination?.buildTarget { + case let .toolchainSDK(platform: platform, _, sdkVariant: sdkVariant): + discriminator = "\(platform)-\(sdkVariant ?? "")" + case let .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple): + discriminator = "\(sdkManifestPath)-\(triple)" + default: + discriminator = "" + } parameters.append(discriminator) } return .init(id: ["target", target.name, target.guid, parameters.joined(separator: ":")].joined(separator: "-")) diff --git a/Sources/SWBCore/DependencyResolution.swift b/Sources/SWBCore/DependencyResolution.swift index 80343091..4217e186 100644 --- a/Sources/SWBCore/DependencyResolution.swift +++ b/Sources/SWBCore/DependencyResolution.swift @@ -213,7 +213,7 @@ struct SpecializationParameters: Hashable, CustomStringConvertible { overrides["SUPPORTS_MACCATALYST"] = sdkVariant.name == MacCatalystInfo.sdkVariantName ? "YES" : "NO" } if let supportedPlatforms { - if let platform = parameters.activeRunDestination?.platform { + if case let .toolchainSDK(platform: platform, _, _) = parameters.activeRunDestination?.buildTarget { // If the specialization matches the platform of the active run destination, we do not need to impose it. if !supportedPlatforms.contains(platform) { overrides["SUPPORTED_PLATFORMS"] = supportedPlatforms.joined(separator: " ") @@ -258,9 +258,12 @@ struct SpecializationParameters: Hashable, CustomStringConvertible { // This seems like an unfortunate way to get from the SDK to its platform. But SettingsBuilder.computeBoundProperties() creates a scope to evaluate the PLATFORM_NAME defined in the SDK's default properties, so maybe there isn't a clearly better way. if let overridingSdk, let overridingPlatform = workspaceContext.core.platformRegistry.platforms.filter({ $0.sdks.contains(where: { $0.canonicalName == overridingSdk.canonicalName }) }).first { platformName = overridingPlatform.name + } else if case let .toolchainSDK(platform: platform, _, _) = parameters.activeRunDestination?.buildTarget { + platformName = platform } else { - platformName = parameters.activeRunDestination?.platform + platformName = nil } + if platformName == nil { if let overridingSdk = overridingSdk { let platformNames = workspaceContext.core.platformRegistry.platforms.map { $0.name } @@ -272,7 +275,7 @@ struct SpecializationParameters: Hashable, CustomStringConvertible { // Otherwise there was no overriding SDK provided, and there is no active run destination (or somehow there's a destination without a platform). This is valid, but it's not clear to me what this means for specialization parameters. } let sdkSuffix: String? - if let sdk = parameters.activeRunDestination?.sdk { + if case let .toolchainSDK(_, sdk: sdk, _) = parameters.activeRunDestination?.buildTarget { if let suffix = try? workspaceContext.sdkRegistry.lookup(sdk, activeRunDestination: parameters.activeRunDestination)?.canonicalNameSuffix, !suffix.isEmpty { sdkSuffix = suffix } else { @@ -282,7 +285,13 @@ struct SpecializationParameters: Hashable, CustomStringConvertible { } else { sdkSuffix = nil } - self.init(workspaceContext: workspaceContext, platformName: platformName, sdkVariantName: parameters.activeRunDestination?.sdkVariant, canonicalNameSuffix: sdkSuffix, diagnostics: diagnostics) + let sdkVariantName: String? + if case let .toolchainSDK(_, _, sdkVariant: sdkVariant) = parameters.activeRunDestination?.buildTarget { + sdkVariantName = sdkVariant + } else { + sdkVariantName = nil + } + self.init(workspaceContext: workspaceContext, platformName: platformName, sdkVariantName: sdkVariantName, canonicalNameSuffix: sdkSuffix, diagnostics: diagnostics) } fileprivate init(workspaceContext: WorkspaceContext, platformName: String?, sdkVariantName: String?, canonicalNameSuffix: String?, diagnostics: [Diagnostic] = []) { @@ -532,12 +541,12 @@ extension SpecializationParameters { let archName: String = platform.determineDefaultArchForIndexArena(preferredArch: workspaceContext.systemInfo?.nativeArchitecture, using: workspaceContext.core) ?? "unknown_arch" for sdkVariant in matchingSDK.variants.keys.sorted() { - let runDestination = RunDestinationInfo(platform: platform.name, sdk: matchingSDK.canonicalName, sdkVariant: sdkVariant, targetArchitecture: archName, supportedArchitectures: [archName], disableOnlyActiveArch: false, hostTargetedPlatform: nil) + let runDestination = RunDestinationInfo(buildTarget: .toolchainSDK(platform: platform.name, sdk: matchingSDK.canonicalName, sdkVariant: sdkVariant), targetArchitecture: archName, supportedArchitectures: [archName], disableOnlyActiveArch: false, hostTargetedPlatform: nil) let buildParams = buildRequest.parameters.replacing(activeRunDestination: runDestination, activeArchitecture: archName) let specializationParams = SpecializationParameters.default(workspaceContext: workspaceContext, buildRequestContext: buildRequestContext, parameters: buildParams) platformBuildParameters.append(PlatformBuildParameters(buildParams: buildParams, specializationParams: specializationParams)) - if runDestination.platform == hostOS && runDestination.sdkVariant == matchingSDK.defaultVariant?.name { + if platform.name == hostOS && sdkVariant == matchingSDK.defaultVariant?.name { hostBuildParameters = platformBuildParameters.last } } @@ -594,14 +603,14 @@ extension SpecializationParameters { for platformParams in platformBuildParametersForIndex { // Before forming all new settings for this platform, do a fast check using `SUPPORTED_PLATFORMS` of `unconfiguredSettings`. // This significantly cuts down the work that this function is doing. - if platformParams.buildParams.activeRunDestination?.sdkVariant == MacCatalystInfo.sdkVariantName { + if case let .toolchainSDK(_, _, sdkVariant: sdkVariant) = platformParams.buildParams.activeRunDestination?.buildTarget, sdkVariant == MacCatalystInfo.sdkVariantName { // macCatalyst has various special rules, check it by forming new settings normally, below. // Carve out once small exception for host tools, which should never build for Catalyst. if let standardTarget = target as? StandardTarget, ProductTypeIdentifier(standardTarget.productTypeIdentifier).isHostBuildTool { continue } } else { - if let platformName = platformParams.buildParams.activeRunDestination?.platform, unconfiguredSupportedPlatforms.count > 0 { + if case let .toolchainSDK(platform: platformName, _, _) = platformParams.buildParams.activeRunDestination?.buildTarget, unconfiguredSupportedPlatforms.count > 0 { guard unconfiguredSupportedPlatforms.contains(platformName) else { continue } } } @@ -649,10 +658,10 @@ extension SpecializationParameters { guard let targetPlatform = settings.platform else { return false } - if targetPlatform.name != runDestination.platform { + if case let .toolchainSDK(platform: platform, _, _) = runDestination.buildTarget, targetPlatform.name != platform { return false } - if settings.sdkVariant?.name != runDestination.sdkVariant { + if case let .toolchainSDK(_, _, sdkVariant: sdkVariant) = runDestination.buildTarget, settings.sdkVariant?.name != sdkVariant { return false } @@ -824,7 +833,7 @@ extension SpecializationParameters { imposedSupportedPlatforms = supportedPlatforms let hostPlatform = settings.globalScope.evaluate(BuiltinMacros.HOST_PLATFORM) - let runDestinationPlatform = buildRequest.parameters.activeRunDestination?.platform + let runDestinationPlatform = if case let .toolchainSDK(platform: platform, _, _) = buildRequest.parameters.activeRunDestination?.buildTarget { platform } else { Optional.none } let supportedPlatformsWithoutSimulators = Set(supportedPlatforms.compactMap { self.workspaceContext.core.platformRegistry.lookup(name: $0) }.filter { !$0.isSimulator }.map { $0.name }) // If the given specialization is unsupported, we still need to impose a platform. diff --git a/Sources/SWBCore/Extensions/PlatformInfoExtension.swift b/Sources/SWBCore/Extensions/PlatformInfoExtension.swift index e1321c31..0e868261 100644 --- a/Sources/SWBCore/Extensions/PlatformInfoExtension.swift +++ b/Sources/SWBCore/Extensions/PlatformInfoExtension.swift @@ -35,6 +35,8 @@ public protocol PlatformInfoExtension: Sendable { func additionalPlatforms(context: any PlatformInfoExtensionAdditionalPlatformsContext) throws -> [(path: Path, data: [String: PropertyListItem])] func adjustPlatformSDKSearchPaths(platformName: String, platformPath: Path, sdkSearchPaths: inout [Path]) + + func platformName(triple: LLVMTriple) -> String? } extension PlatformInfoExtension { @@ -64,6 +66,10 @@ extension PlatformInfoExtension { public func adjustPlatformSDKSearchPaths(platformName: String, platformPath: Path, sdkSearchPaths: inout [Path]) { } + + public func platformName(triple: LLVMTriple) -> String? { + return nil + } } public protocol PlatformInfoExtensionAdditionalPlatformsContext: Sendable { diff --git a/Sources/SWBCore/SDKRegistry.swift b/Sources/SWBCore/SDKRegistry.swift index 3a8dc465..2642e609 100644 --- a/Sources/SWBCore/SDKRegistry.swift +++ b/Sources/SWBCore/SDKRegistry.swift @@ -515,6 +515,9 @@ public protocol SDKRegistryLookup: Sendable { /// - returns: The found `SDK`, or `nil` if no SDK with the given name could be found or `name` was in an invalid format. func lookup(_ name: String, activeRunDestination: RunDestinationInfo?) throws -> SDK? + /// Synthesize an SDK for the given platform with the given manifest JSON file path + func synthesizedSDK(platform: Platform, sdkManifestPath: String, triple: String) throws -> SDK? + /// Look up the SDK with the given path. If the registry is immutable, then this will only return the SDK if it was loaded when the registry was created; only mutable registries can discover and load new SDKs after that point. /// - parameter path: Absolute path of the SDK to look up. /// - returns: The found `SDK`, or `nil` if no SDK was found at the given path. @@ -1153,6 +1156,95 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send return sdk } + public func synthesizedSDK(platform: Platform, sdkManifestPath: String, triple: String) throws -> SDK? { + // Let's check the active run destination to see if there's an SDK path that we should be using + let llvmTriple = try LLVMTriple(triple) + + let host = hostOperatingSystem + + // Don't allow re-registering the same SDK + if let existing = sdksByPath[Path(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", + + "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(["$(inherited)"] + extraSwiftCompilerSettings.map( {.plString($0)} )), // The extra swift compiler settings in JSON are intended to go to the linker driver too + ]), + "SupportedTargets": .plDict([ + platform.name: .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([]) + ])) + + if let sdk { + try sdk.loadExtendedInfo(delegate.namespace) + sdksByPath[Path(sdkManifestPath)] = sdk + return sdk + } + } + } + + return nil + } + public func lookup(nameOrPath key: String, basePath: Path, activeRunDestination: RunDestinationInfo?) throws -> SDK? { #if !os(Windows) // TODO: Turn this validation back on once our path handling is cleaned up a bit more @@ -1202,7 +1294,14 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send // If we got here, we have DriverKit SDKs (the only ones which use cohort platforms) for multiple cohort platforms. Narrow down the list by disambiguating using the run destination. func selectSDKList() -> [SDK]? { - if let list = sdksByCohortPlatform[activeRunDestination?.platform] { + let platform: String? + if case let .toolchainSDK(platform: p, _, _) = activeRunDestination?.buildTarget { + platform = p + } else { + platform = nil + } + + if let list = sdksByCohortPlatform[platform] { return list } @@ -1210,7 +1309,8 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send // by the cohort platforms of the run destination's SDK's platform. This is necessary to resolve // driverkit when we have a DriverKit run destination but with a platform-specific SDK. if let runDestination = activeRunDestination, - let cohortPlatforms = try? lookup(runDestination.sdk, activeRunDestination: nil)?.cohortPlatforms { + case let .toolchainSDK(_, sdk: sdk, _) = runDestination.buildTarget, + let cohortPlatforms = try? lookup(sdk, activeRunDestination: nil)?.cohortPlatforms { for cohortPlatform in cohortPlatforms { if let list = sdksByCohortPlatform[cohortPlatform] { return list diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index cc6f3283..d9178b76 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -1265,6 +1265,32 @@ private class SettingsBuilder: ProjectMatchLookup { } } + enum FindPlatformError: Error { case message(String) } + + func findPlatform(for triple: String, core: Core) -> Result { + let llvmTriple: LLVMTriple + + do { + llvmTriple = try LLVMTriple(triple) + } catch { + return .failure(.message("\(error)")) + } + + let platformNames = core.pluginManager.extensions(of: PlatformInfoExtensionPoint.self).compactMap({ $0.platformName(triple: llvmTriple) }).sorted() + + guard let platformName = platformNames.only else { + return .failure(.message("unable to find a single platform name for triple '\(triple)'. results: \(platformNames)")) + } + + // FIXME fall back on a platform if none can be found to match the triple + + guard let platform = core.platformRegistry.lookup(name: platformName) else { + return .failure(.message("unable to find platform for '\(platformName)'")) + } + + return .success(platform) + } + // Properties the builder was initialized with. let workspaceContext: WorkspaceContext @@ -1521,9 +1547,10 @@ private class SettingsBuilder: ProjectMatchLookup { // Add the SDK overrides. if let sdk = boundProperties.sdk { let scope = createScope(sdkToUse: sdk) - let destinationIsMacCatalyst = parameters.activeRunDestination?.sdkVariant == MacCatalystInfo.sdkVariantName let supportsMacCatalyst = Settings.supportsMacCatalyst(scope: scope, core: core) - if destinationIsMacCatalyst && supportsMacCatalyst { + if case let .toolchainSDK(platform: _, sdk: _, sdkVariant: sdkVariant) = parameters.activeRunDestination?.buildTarget, + sdkVariant == MacCatalystInfo.sdkVariantName, + supportsMacCatalyst { pushTable(.exported) { $0.push(BuiltinMacros.SUPPORTED_PLATFORMS, BuiltinMacros.namespace.parseStringList(["$(inherited)", "macosx"])) } @@ -1782,8 +1809,9 @@ private class SettingsBuilder: ProjectMatchLookup { // We will replace SDKROOT values of "auto" here if the run destination is compatible. let usesReplaceableAutomaticSDKRoot: Bool - if sdkroot == "auto", let activePlatform = parameters.activeRunDestination?.platform { - let destinationIsMacCatalyst = parameters.activeRunDestination?.sdkVariant == MacCatalystInfo.sdkVariantName + if sdkroot == "auto", + case let .toolchainSDK(platform: activePlatform, sdk: _, sdkVariant: sdkVariant) = parameters.activeRunDestination?.buildTarget { + let destinationIsMacCatalyst = sdkVariant == MacCatalystInfo.sdkVariantName let scope = createScope(effectiveTargetConfig, sdkToUse: sdk) let supportedPlatforms = scope.evaluate(BuiltinMacros.SUPPORTED_PLATFORMS) @@ -1798,20 +1826,40 @@ private class SettingsBuilder: ProjectMatchLookup { } else { usesReplaceableAutomaticSDKRoot = false } - if usesReplaceableAutomaticSDKRoot, let activeSDK = parameters.activeRunDestination?.sdk { + if usesReplaceableAutomaticSDKRoot, + case let .toolchainSDK(platform: _, sdk: activeSDK, sdkVariant: _) = parameters.activeRunDestination?.buildTarget { sdkroot = activeSDK } do { - sdk = try project.map { try sdkRegistry.lookup(nameOrPath: sdkroot, basePath: $0.sourceRoot, activeRunDestination: parameters.activeRunDestination) } ?? nil + sdk = try project.map { + switch parameters.activeRunDestination?.buildTarget { + case let .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple): + let findPlatformResult = findPlatform(for: triple, core: core) + let platform: Platform + + switch findPlatformResult { + case let .failure(.message(msg)): + errors.append(msg) + return nil + case let .success(platform: p): + platform = p + } + + return try sdkRegistry.synthesizedSDK(platform: platform, sdkManifestPath: sdkManifestPath, triple: triple) + default: + return try sdkRegistry.lookup(nameOrPath: sdkroot, basePath: $0.sourceRoot, activeRunDestination: parameters.activeRunDestination) + } + } ?? nil } catch { sdk = nil sdkLookupErrors.append(error) } + if let s = sdk { // Evaluate the SDK variant, if there is one. let sdkVariantName: String - if usesReplaceableAutomaticSDKRoot, let activeSDKVariant = parameters.activeRunDestination?.sdkVariant { + if usesReplaceableAutomaticSDKRoot, case let .toolchainSDK(platform: _, sdk: _, sdkVariant: activeSDKVariant) = parameters.activeRunDestination?.buildTarget, let activeSDKVariant { sdkVariantName = activeSDKVariant } else { sdkVariantName = createScope(effectiveTargetConfig, sdkToUse: s).evaluate(BuiltinMacros.SDK_VARIANT) @@ -3512,23 +3560,48 @@ private class SettingsBuilder: ProjectMatchLookup { // Destination info: since runDestination.{platform,sdk} were set by the IDE, we expect them to resolve in Swift Build correctly guard let runDestination = self.parameters.activeRunDestination else { return } - guard let destinationPlatform: Platform = self.core.platformRegistry.lookup(name: runDestination.platform) else { - self.errors.append("unable to resolve run destination platform: '\(runDestination.platform)'") - return - } + + let destinationPlatform: Platform let destinationSDK: SDK - do { - guard let sdk = try sdkRegistry.lookup(runDestination.sdk, activeRunDestination: runDestination) else { - self.errors.append("unable to resolve run destination SDK: '\(runDestination.sdk)'") + switch runDestination.buildTarget { + case let .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple): + let findPlatformResult = findPlatform(for: triple, core: core) + + switch findPlatformResult { + case let .failure(.message(msg)): + self.errors.append(msg) + return + case let .success(platform): + destinationPlatform = platform + } + + guard let sdk = try? sdkRegistry.synthesizedSDK(platform: destinationPlatform, sdkManifestPath: sdkManifestPath, triple: triple) else { + self.errors.append("unable to synthesize SDK for Swift SDK build target: '\(runDestination.buildTarget)'") return } destinationSDK = sdk - } catch let error as AmbiguousSDKLookupError { - self.diagnostics.append(error.diagnostic) - return - } catch { - self.errors.append("\(error)") - return + case let .toolchainSDK(platform: platform, sdk: sdk, _): + guard let platform = self.core.platformRegistry.lookup(name: platform) else { + self.errors.append("unable to resolve run destination platform: '\(platform)'") + return + } + + destinationPlatform = platform + + do { + if let sdk = try sdkRegistry.lookup(sdk, activeRunDestination: runDestination) { + destinationSDK = sdk + } else { + self.errors.append("unable to resolve run destination SDK: '\(sdk)'") + return + } + } catch let error as AmbiguousSDKLookupError { + self.diagnostics.append(error.diagnostic) + return + } catch { + self.errors.append("\(error)") + return + } } let destinationPlatformIsMacOS = destinationPlatform.name == "macosx" @@ -3541,7 +3614,9 @@ private class SettingsBuilder: ProjectMatchLookup { do { let scope = createScope(sdkToUse: nil) - let destinationIsMacCatalyst = runDestination.sdkVariant == MacCatalystInfo.sdkVariantName + let destinationIsMacCatalyst = if case let .toolchainSDK(platform: _, sdk: _, sdkVariant: sdkVariant) = runDestination.buildTarget { + sdkVariant == MacCatalystInfo.sdkVariantName + } else { false } let supportsMacCatalyst = Settings.supportsMacCatalyst(scope: scope, core: core) if destinationIsMacCatalyst && supportsMacCatalyst { pushTable(.exported) { @@ -3639,9 +3714,26 @@ private class SettingsBuilder: ProjectMatchLookup { // Destination info: since runDestination.{platform,sdk} were set by the IDE, we expect them to resolve in Swift Build correctly guard let runDestination = self.parameters.activeRunDestination else { return } - guard let destinationPlatform: Platform = self.core.platformRegistry.lookup(name: runDestination.platform) else { - self.errors.append("unable to resolve run destination platform: '\(runDestination.platform)'") - return + + let destinationPlatform: Platform + + switch runDestination.buildTarget { + case let .swiftSDK(_, triple: triple): + let findPlatformResult = findPlatform(for: triple, core: core) + + switch findPlatformResult { + case let .failure(.message(msg)): + self.errors.append(msg) + return + case let .success(p): + destinationPlatform = p + } + case let .toolchainSDK(platform: platformName, sdk: _, sdkVariant: _): + guard let platform: Platform = self.core.platformRegistry.lookup(name: platformName) else { + self.errors.append("unable to resolve run destination platform: '\(platformName)'") + return + } + destinationPlatform = platform } // Target info diff --git a/Sources/SWBCore/SwiftSDK.swift b/Sources/SWBCore/SwiftSDK.swift index c8f7f43f..7edbb71b 100644 --- a/Sources/SWBCore/SwiftSDK.swift +++ b/Sources/SWBCore/SwiftSDK.swift @@ -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. @@ -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 } @@ -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 [] } diff --git a/Sources/SWBCore/WorkspaceContext.swift b/Sources/SWBCore/WorkspaceContext.swift index 5f0e4f6b..1b8a3cc5 100644 --- a/Sources/SWBCore/WorkspaceContext.swift +++ b/Sources/SWBCore/WorkspaceContext.swift @@ -213,6 +213,10 @@ public struct WorkspaceContextSDKRegistry: SDKRegistryLookup, Sendable { public func lookup(nameOrPath: String, basePath: Path, activeRunDestination: RunDestinationInfo?) throws -> SDK? { return try lookupInEach { try $0.lookup(nameOrPath: nameOrPath, basePath: basePath, activeRunDestination: activeRunDestination) } } + + func synthesizedSDK(platform: Platform, sdkManifestPath: String, triple: String) throws -> SDK? { + return try lookupInEach { try $0.synthesizedSDK(platform: platform, sdkManifestPath: sdkManifestPath, triple: triple) } + } } @_spi(Testing) public init(coreSDKRegistry: SDKRegistry, delegate: any SDKRegistryDelegate, userNamespace: MacroNamespace, overridingSDKsDir: Path?) { @@ -246,6 +250,10 @@ public struct WorkspaceContextSDKRegistry: SDKRegistryLookup, Sendable { public func lookup(nameOrPath: String, basePath: Path, activeRunDestination: RunDestinationInfo?) throws -> SDK? { return try underlyingLookup.lookup(nameOrPath: nameOrPath, basePath: basePath, activeRunDestination: activeRunDestination) } + + public func synthesizedSDK(platform: Platform, sdkManifestPath: String, triple: String) throws -> SDK? { + return try underlyingLookup.synthesizedSDK(platform: platform, sdkManifestPath: sdkManifestPath, triple: triple) + } } /// Wrapper for information needed to use a workspace. diff --git a/Sources/SWBProtocol/MessageSupport.swift b/Sources/SWBProtocol/MessageSupport.swift index 65e79bfc..16ab4383 100644 --- a/Sources/SWBProtocol/MessageSupport.swift +++ b/Sources/SWBProtocol/MessageSupport.swift @@ -318,23 +318,75 @@ public struct BuildParametersMessagePayload: SerializableCodable, Equatable, Sen } public struct RunDestinationInfo: SerializableCodable, Hashable, Sendable { - public var platform: String - public var sdk: String - public var sdkVariant: String? + public var buildTarget: BuildTarget + public var targetArchitecture: String public var supportedArchitectures: OrderedSet public var disableOnlyActiveArch: Bool public var hostTargetedPlatform: String? - public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: OrderedSet, disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) { - self.platform = platform - self.sdk = sdk - self.sdkVariant = sdkVariant + public init(buildTarget: BuildTarget, targetArchitecture: String, supportedArchitectures: OrderedSet, disableOnlyActiveArch: Bool, hostTargetedPlatform: String? = nil) { + self.buildTarget = buildTarget self.targetArchitecture = targetArchitecture self.supportedArchitectures = supportedArchitectures self.disableOnlyActiveArch = disableOnlyActiveArch self.hostTargetedPlatform = hostTargetedPlatform } + + public enum CodingKeys: CodingKey { + case buildTarget + case targetArchitecture + case supportedArchitectures + case disableOnlyActiveArch + case hostTargetedPlatform + + // These are the old coding keys that were previously associated with toolchain SDK's + case platform + case sdk + case sdkVariant + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if let buildTarget = try container.decodeIfPresent(BuildTarget.self, forKey: .buildTarget) { + self.buildTarget = buildTarget + } else { + // Handle the message payload from earlier versions that didn't have the buildTarget enumeration + let platform = try container.decode(String.self, forKey: .platform) + let sdk: String = try container.decode(String.self, forKey: .sdk) + let sdkVariant: String? = try container.decode(String?.self, forKey: .sdkVariant) + self.buildTarget = .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant) + } + + self.targetArchitecture = try container.decode(String.self, forKey: .targetArchitecture) + self.supportedArchitectures = try container.decode(OrderedSet.self, forKey: .supportedArchitectures) + self.disableOnlyActiveArch = try container.decode(Bool.self, forKey: .disableOnlyActiveArch) + self.hostTargetedPlatform = try container.decodeIfPresent(String.self, forKey: .hostTargetedPlatform) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self.buildTarget { + case let .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant): + try container.encode(platform, forKey: .platform) + try container.encode(sdk, forKey: .sdk) + try container.encode(sdkVariant, forKey: .sdkVariant) + case .swiftSDK: + try container.encode(buildTarget, forKey: .buildTarget) + } + + try container.encode(self.targetArchitecture, forKey: .targetArchitecture) + try container.encode(self.supportedArchitectures, forKey: .supportedArchitectures) + try container.encode(self.disableOnlyActiveArch, forKey: .disableOnlyActiveArch) + try container.encode(self.hostTargetedPlatform, forKey: .hostTargetedPlatform) + } +} + +public enum BuildTarget: SerializableCodable, Hashable, Sendable { + case toolchainSDK(platform: String, sdk: String, sdkVariant: String?) + case swiftSDK(sdkManifestPath: String, triple: String) } /// The arena info being sent in a Message. diff --git a/Sources/SWBTaskConstruction/StaleFileRemovalContext.swift b/Sources/SWBTaskConstruction/StaleFileRemovalContext.swift index 3c742681..7c80a43c 100644 --- a/Sources/SWBTaskConstruction/StaleFileRemovalContext.swift +++ b/Sources/SWBTaskConstruction/StaleFileRemovalContext.swift @@ -60,9 +60,9 @@ extension StaleFileRemovalContext { } else { staleFileRemovalIdentifier += "workspace" staleFileRemovalIdentifier += "-\(globalProductPlan.planRequest.buildRequest.parameters.configuration ?? "none")" - if let destination = globalProductPlan.planRequest.buildRequest.parameters.activeRunDestination { - staleFileRemovalIdentifier += "-\(destination.sdk)" - if let variant = destination.sdkVariant { + if case let .toolchainSDK(_, sdk: sdk, sdkVariant: variant) = globalProductPlan.planRequest.buildRequest.parameters.activeRunDestination?.buildTarget { + staleFileRemovalIdentifier += "-\(sdk)" + if let variant { staleFileRemovalIdentifier += "-\(variant)" } } diff --git a/Sources/SWBTaskExecution/ProjectPlanner.swift b/Sources/SWBTaskExecution/ProjectPlanner.swift index 287c35b7..7fe87669 100644 --- a/Sources/SWBTaskExecution/ProjectPlanner.swift +++ b/Sources/SWBTaskExecution/ProjectPlanner.swift @@ -136,6 +136,6 @@ private extension BuildParameters { // All relevant platforms define a preferredArch, so the undefined_arch fallback case should never happen // in practice, and indicates a serious issue occurred during plugin loading. let targetArchitecture = platform.preferredArch ?? "undefined_arch" - return RunDestinationInfo(platform: platform.name, sdk: platform.name, sdkVariant: nil, targetArchitecture: targetArchitecture, supportedArchitectures: [targetArchitecture], disableOnlyActiveArch: false, hostTargetedPlatform: nil) + return RunDestinationInfo(buildTarget: .toolchainSDK(platform: platform.name, sdk: platform.name, sdkVariant: nil), targetArchitecture: targetArchitecture, supportedArchitectures: [targetArchitecture], disableOnlyActiveArch: false, hostTargetedPlatform: nil) } } diff --git a/Sources/SWBTaskExecution/TaskActions/ValidateProductTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/ValidateProductTaskAction.swift index 398f01ee..6a3de8ef 100644 --- a/Sources/SWBTaskExecution/TaskActions/ValidateProductTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/ValidateProductTaskAction.swift @@ -188,7 +188,7 @@ public final class ValidateProductTaskAction: TaskAction { // Validate the iPad multitasking/splitview support in the Info.plist. This never causes the tool to fail, but it may emit warnings. validateiPadMultiTaskingSplitViewSupport(infoPlist, outputDelegate: outputDelegate) - if let configuredTarget = task.forTarget, let platform = configuredTarget.parameters.activeRunDestination?.platform { + if let configuredTarget = task.forTarget, case let .toolchainSDK(platform: platform, _, _) = configuredTarget.parameters.activeRunDestination?.buildTarget { // Validate that this is actually an App Store category. validateAppStoreCategory(infoPlist, platform: platform, targetName: configuredTarget.target.name, schemeCommand: executionDelegate.schemeCommand, options: options, outputDelegate: outputDelegate) diff --git a/Sources/SWBTestSupport/RunDestinationTestSupport.swift b/Sources/SWBTestSupport/RunDestinationTestSupport.swift index fa29f90c..e00cf066 100644 --- a/Sources/SWBTestSupport/RunDestinationTestSupport.swift +++ b/Sources/SWBTestSupport/RunDestinationTestSupport.swift @@ -19,6 +19,7 @@ import Foundation package protocol _RunDestinationInfo { init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool) + // These assume that this is an Apple SDK build target, otherwise you get default values var platform: String { get } var sdk: String { get } var sdkVariant: String? { get } @@ -299,20 +300,28 @@ extension RunDestinationInfo { /// /// - note: Returns `nil` for non-Mach-O platforms such as Linux. package func buildVersionPlatform(_ core: Core) -> BuildVersion.Platform? { - guard let sdk = try? core.sdkRegistry.lookup(sdk, activeRunDestination: self) else { return nil } + guard case let .toolchainSDK(_, sdk: sdk, sdkVariant: sdkVariant) = buildTarget, + let sdk = try? core.sdkRegistry.lookup(sdk, activeRunDestination: self) else { + return nil + } return sdk.targetBuildVersionPlatform(sdkVariant: sdkVariant.map { sdkVariant in sdk.variant(for: sdkVariant) } ?? sdk.defaultVariant) } package func imageFormat(_ core: Core) -> ImageFormat { - switch platform { - case "webassembly": + switch buildTarget { + case let .toolchainSDK(platform: platform, _, _): + switch platform { + case "webassembly": + fatalError("not implemented") + case "windows": + return .pe + case _ where buildVersionPlatform(core) != nil: + return .macho + default: + return .elf + } + case .swiftSDK: fatalError("not implemented") - case "windows": - return .pe - case _ where buildVersionPlatform(core) != nil: - return .macho - default: - return .elf } } @@ -326,7 +335,17 @@ extension RunDestinationInfo { } switch imageFormat(core) { case .elf: - environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(platform)").str) + switch buildTarget { + case let .toolchainSDK(platform, _, _): + environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(platform)").str) + case let .swiftSDK(_, triple): + guard let llvmTriple = try? LLVMTriple(triple) else { + // Fall back to the OS provided Swift runtime + break + } + + environment.prependPath(key: "LD_LIBRARY_PATH", value: toolchain.path.join("usr/lib/swift/\(llvmTriple.system)").str) + } case .pe: environment.prependPath(key: .path, value: core.developerPath.path.join("Runtimes").join(toolchain.version.description).join("usr/bin").str) case .macho: @@ -385,6 +404,30 @@ extension _RunDestinationInfo { extension RunDestinationInfo: _RunDestinationInfo { package init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool) { - self.init(platform: platform, sdk: sdk, sdkVariant: sdkVariant, targetArchitecture: targetArchitecture, supportedArchitectures: OrderedSet(supportedArchitectures), disableOnlyActiveArch: disableOnlyActiveArch, hostTargetedPlatform: nil) + self.init(buildTarget: .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant), targetArchitecture: targetArchitecture, supportedArchitectures: OrderedSet(supportedArchitectures), disableOnlyActiveArch: disableOnlyActiveArch, hostTargetedPlatform: nil) + } + + package var platform: String { + guard case let .toolchainSDK(platform: platform, _, _) = buildTarget else { + return "" + } + + return platform + } + + package var sdk: String { + guard case let .toolchainSDK(_, sdk: sdk, _) = buildTarget else { + return "" + } + + return sdk + } + + package var sdkVariant: String? { + guard case let .toolchainSDK(_, _, sdkVariant: sdkVariant) = buildTarget else { + return nil + } + + return sdkVariant } } diff --git a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec index 40022fee..362585fc 100644 --- a/Sources/SWBUniversalPlatform/Specs/Ld.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Ld.xcspec @@ -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; diff --git a/Sources/SWBWebAssemblyPlatform/Plugin.swift b/Sources/SWBWebAssemblyPlatform/Plugin.swift index dbc99e82..e96c8e2c 100644 --- a/Sources/SWBWebAssemblyPlatform/Plugin.swift +++ b/Sources/SWBWebAssemblyPlatform/Plugin.swift @@ -45,6 +45,14 @@ struct WebAssemblyPlatformExtension: PlatformInfoExtension { ]) ] } + + func platformName(triple: LLVMTriple) -> String? { + if triple.system.hasPrefix("wasi") { + return "webassembly" + } + + return nil + } } // TODO: We currently hardcode WebAssembly-specific information here but diff --git a/Sources/SWBWebAssemblyPlatform/Specs/WasmLd.xcspec b/Sources/SWBWebAssemblyPlatform/Specs/WasmLd.xcspec index ba106172..297675ea 100644 --- a/Sources/SWBWebAssemblyPlatform/Specs/WasmLd.xcspec +++ b/Sources/SWBWebAssemblyPlatform/Specs/WasmLd.xcspec @@ -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; @@ -129,6 +137,14 @@ Type = Path; DefaultValue = ""; }, + { + Name = SWIFTC_SDKROOT_LINKER_INPUT; + Type = Path; + DefaultValue = "$(SDKROOT)"; + Condition = "$(LINKER_DRIVER) == swiftc"; + CommandLineFlag = "-sdk"; + IsInputDependency = Yes; + }, { Name = "__INPUT_FILE_LIST_PATH__"; Type = Path; diff --git a/Sources/SWBWebAssemblyPlatform/Specs/WebAssembly.xcspec b/Sources/SWBWebAssemblyPlatform/Specs/WebAssembly.xcspec index b7cfeaf8..5a30bd12 100644 --- a/Sources/SWBWebAssemblyPlatform/Specs/WebAssembly.xcspec +++ b/Sources/SWBWebAssemblyPlatform/Specs/WebAssembly.xcspec @@ -23,4 +23,10 @@ ); SortNumber = 0; }, + { + Domain = webassembly; + Type = ProductType; + Identifier = org.swift.product-type.common.object; + BasedOn = com.apple.product-type.library.static; + }, ) diff --git a/Sources/SwiftBuild/SWBBuildParameters.swift b/Sources/SwiftBuild/SWBBuildParameters.swift index 11d07745..e9354521 100644 --- a/Sources/SwiftBuild/SWBBuildParameters.swift +++ b/Sources/SwiftBuild/SWBBuildParameters.swift @@ -36,29 +36,179 @@ public struct SWBBuildParameters: Codable, Sendable { /// Refer to `SWBProtocol.RunDestinationInfo` public struct SWBRunDestinationInfo: Codable, Sendable { - public var platform: String - public var sdk: String - public var sdkVariant: String? + public struct SWBBuildTarget: Codable, Sendable { + var _internalBuildTarget: InternalBuildTarget + + public static func toolchainSDK(platform: String, sdk: String, sdkVariant: String?) -> SWBBuildTarget { + SWBBuildTarget(_internalBuildTarget: .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant)) + } + + public static func swiftSDK(sdkManifestPath: String, triple: String) -> SWBBuildTarget { + SWBBuildTarget(_internalBuildTarget: .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple)) + } + } + + public var buildTarget: SWBBuildTarget public var targetArchitecture: String public var supportedArchitectures: [String] public var disableOnlyActiveArch: Bool public var hostTargetedPlatform: String? + public enum CodingKeys: CodingKey { + case buildTarget + case targetArchitecture + case supportedArchitectures + case disableOnlyActiveArch + case hostTargetedPlatform + + // These are the old coding keys that were previously associated with toolchain SDK's + case platform + case sdk + case sdkVariant + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if let buildTarget = try container.decodeIfPresent(SWBBuildTarget.self, forKey: .buildTarget) { + self.buildTarget = buildTarget + } else { + // Handle the message payload from earlier versions that didn't have the buildTarget enumeration + let platform = try container.decode(String.self, forKey: .platform) + let sdk: String = try container.decode(String.self, forKey: .sdk) + let sdkVariant: String? = try container.decode(String?.self, forKey: .sdkVariant) + self.buildTarget = .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant) + } + + self.targetArchitecture = try container.decode(String.self, forKey: .targetArchitecture) + self.supportedArchitectures = try container.decode([String].self, forKey: .supportedArchitectures) + self.disableOnlyActiveArch = try container.decode(Bool.self, forKey: .disableOnlyActiveArch) + self.hostTargetedPlatform = try container.decodeIfPresent(String.self, forKey: .hostTargetedPlatform) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self.buildTarget._internalBuildTarget { + case let .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant): + try container.encode(platform, forKey: .platform) + try container.encode(sdk, forKey: .sdk) + try container.encode(sdkVariant, forKey: .sdkVariant) + case .swiftSDK: + try container.encode(buildTarget, forKey: .buildTarget) + } + + try container.encode(self.targetArchitecture, forKey: .targetArchitecture) + try container.encode(self.supportedArchitectures, forKey: .supportedArchitectures) + try container.encode(self.disableOnlyActiveArch, forKey: .disableOnlyActiveArch) + try container.encode(self.hostTargetedPlatform, forKey: .hostTargetedPlatform) + } + + @available(*, deprecated, message: "Use buildTarget and match on the toolchainSDK case instead") + public var platform: String { + guard case let .toolchainSDK(platform: platform, _, _) = buildTarget._internalBuildTarget else { + return "" + } + + return platform + } + + @available(*, deprecated, message: "Use buildTarget and match on the toolchainSDK case instead") + public var sdk: String { + guard case let .toolchainSDK(_, sdk: sdk, _) = buildTarget._internalBuildTarget else { + return "" + } + + return sdk + } + + @available(*, deprecated, message: "Use buildTarget and match on the toolchainSDK case instead") + public var sdkVariant: String? { + guard case let .toolchainSDK(_, _, sdkVariant: sdkVariant) = buildTarget._internalBuildTarget else { + return nil + } + + return sdkVariant + } + public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool) { - self.platform = platform - self.sdk = sdk - self.sdkVariant = sdkVariant + self.buildTarget = .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant) self.targetArchitecture = targetArchitecture self.supportedArchitectures = supportedArchitectures self.disableOnlyActiveArch = disableOnlyActiveArch } + public init(buildTarget: SWBBuildTarget, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool, hostTargetedPlatform: String? = nil) { + self.buildTarget = buildTarget + self.targetArchitecture = targetArchitecture + self.supportedArchitectures = supportedArchitectures + self.disableOnlyActiveArch = disableOnlyActiveArch + self.hostTargetedPlatform = hostTargetedPlatform + } + public init(platform: String, sdk: String, sdkVariant: String?, targetArchitecture: String, supportedArchitectures: [String], disableOnlyActiveArch: Bool, hostTargetedPlatform: String?) { self.init(platform: platform, sdk: sdk, sdkVariant: sdkVariant, targetArchitecture: targetArchitecture, supportedArchitectures: supportedArchitectures, disableOnlyActiveArch: disableOnlyActiveArch) self.hostTargetedPlatform = hostTargetedPlatform } } +enum InternalBuildTarget: Codable, Sendable { + case toolchainSDK(platform: String, sdk: String, sdkVariant: String?) + case swiftSDK(sdkManifestPath: String, triple: String) + + private enum CodingKeys: String, CodingKey { + // Selector + case buildTarget + + // Apple SDK + case platform + case sdk + case sdkVariant + + // Swift SDK + case sdkManifestPath + case triple + } + + private enum BuildTarget: String, Codable { + case toolchainSDK + case swiftSDK + + init(_ buildTarget: InternalBuildTarget) { + switch buildTarget { + case .toolchainSDK: + self = .toolchainSDK + case .swiftSDK: + self = .swiftSDK + } + } + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch try container.decode(BuildTarget.self, forKey: .buildTarget) { + case .toolchainSDK: + self = try .toolchainSDK(platform: container.decode(String.self, forKey: .platform), sdk: container.decode(String.self, forKey: .sdk), sdkVariant: container.decode(String?.self, forKey: .sdkVariant)) + case .swiftSDK: + self = try .swiftSDK(sdkManifestPath: container.decode(String.self, forKey: .sdkManifestPath), triple: container.decode(String.self, forKey: .triple)) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(BuildTarget(self), forKey: .buildTarget) + switch self { + case let .toolchainSDK(platform, sdk, sdkVariant): + try container.encode(platform, forKey: .platform) + try container.encode(sdk, forKey: .sdk) + try container.encode(sdkVariant, forKey: .sdkVariant) + case let .swiftSDK(sdkManifestPath, triple): + try container.encode(sdkManifestPath, forKey: .sdkManifestPath) + try container.encode(triple, forKey: .triple) + } + } +} + /// Refer to `SWBProtocol.ArenaInfo` public struct SWBArenaInfo: Codable, Sendable { public var derivedDataPath: String diff --git a/Sources/SwiftBuild/SWBBuildServiceSession.swift b/Sources/SwiftBuild/SWBBuildServiceSession.swift index b86e3a52..972fcfe8 100644 --- a/Sources/SwiftBuild/SWBBuildServiceSession.swift +++ b/Sources/SwiftBuild/SWBBuildServiceSession.swift @@ -838,7 +838,12 @@ 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) + switch x.buildTarget._internalBuildTarget { + case let .toolchainSDK(platform, sdk, sdkVariant): + self.init(buildTarget: .toolchainSDK(platform: platform, sdk: sdk, sdkVariant: sdkVariant), targetArchitecture: x.targetArchitecture, supportedArchitectures: OrderedSet(x.supportedArchitectures), disableOnlyActiveArch: x.disableOnlyActiveArch, hostTargetedPlatform: x.hostTargetedPlatform) + case let .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple): + self.init(buildTarget: .swiftSDK(sdkManifestPath: sdkManifestPath, triple: triple), targetArchitecture: x.targetArchitecture, supportedArchitectures: OrderedSet(x.supportedArchitectures), disableOnlyActiveArch: x.disableOnlyActiveArch, hostTargetedPlatform: x.hostTargetedPlatform) + } } } diff --git a/Tests/SWBProtocolTests/MessageSerializationTests.swift b/Tests/SWBProtocolTests/MessageSerializationTests.swift index 17445420..895b461b 100644 --- a/Tests/SWBProtocolTests/MessageSerializationTests.swift +++ b/Tests/SWBProtocolTests/MessageSerializationTests.swift @@ -24,9 +24,11 @@ import Testing action: "build", configuration: "Debug", activeRunDestination: RunDestinationInfo( - platform: "osOS", - sdk: "osos", - sdkVariant: "x", + buildTarget: .toolchainSDK( + platform: "osOS", + sdk: "osos", + sdkVariant: "x", + ), targetArchitecture: "arm128", supportedArchitectures: ["arm128"], disableOnlyActiveArch: true,