diff --git a/Sources/GeneratorCLI/GeneratorCLI.swift b/Sources/GeneratorCLI/GeneratorCLI.swift index d1a5432..e75f2ed 100644 --- a/Sources/GeneratorCLI/GeneratorCLI.swift +++ b/Sources/GeneratorCLI/GeneratorCLI.swift @@ -141,12 +141,13 @@ extension GeneratorCLI { var targetSwiftPackagePath: String? = nil @Option( + name: .customLong("host"), help: """ - The host triple of the bundle. Defaults to a triple of the machine this generator is \ - running on if unspecified. + The host triples of the bundle. Defaults to a triple or triples of the machine this generator is \ + running on if unspecified. Multiple host triples are only supported for macOS hosts. """ ) - var host: Triple? = nil + var hosts: [Triple] = [] @Option( help: @@ -174,9 +175,9 @@ extension GeneratorCLI { #endif } - func deriveHostTriple() throws -> Triple { - if let host { - return host + func deriveHostTriples() throws -> [Triple] { + if !hosts.isEmpty { + return hosts } let current = try SwiftSDKGenerator.getCurrentTriple(isVerbose: self.verbose) if let arch = hostArch { @@ -184,9 +185,16 @@ extension GeneratorCLI { appLogger.warning( "deprecated: Please use `--host \(target.triple)` instead of `--host-arch \(arch)`" ) - return target + return [target] + } + // macOS toolchains are built as universal binaries + if current.isMacOSX { + return [ + Triple("arm64-apple-macos"), + Triple("x86_64-apple-macos"), + ] } - return current + return [current] } } @@ -230,7 +238,7 @@ extension GeneratorCLI { ) var distributionVersion: String? - func deriveTargetTriple(hostTriple: Triple) -> Triple { + func deriveTargetTriple(hostTriples: [Triple]) -> Triple { if let target = generatorOptions.target { return target } @@ -241,7 +249,13 @@ extension GeneratorCLI { ) return target } - return Triple(arch: hostTriple.arch!, vendor: nil, os: .linux, environment: .gnu) + let arch: Triple.Arch + if hostTriples.count == 1, let hostTriple = hostTriples.first { + arch = hostTriple.arch! + } else { + arch = try! SwiftSDKGenerator.getCurrentTriple(isVerbose: false).arch! + } + return Triple(arch: arch, vendor: nil, os: .linux, environment: .gnu) } func run() async throws { @@ -265,12 +279,12 @@ extension GeneratorCLI { name: distributionName, version: distributionVersion ) - let hostTriple = try self.generatorOptions.deriveHostTriple() - let targetTriple = self.deriveTargetTriple(hostTriple: hostTriple) + let hostTriples = try self.generatorOptions.deriveHostTriples() + let targetTriple = self.deriveTargetTriple(hostTriples: hostTriples) let recipe = try LinuxRecipe( targetTriple: targetTriple, - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: generatorOptions.swiftVersion, swiftBranch: self.generatorOptions.swiftBranch, @@ -333,8 +347,8 @@ extension GeneratorCLI { } let recipe = try WebAssemblyRecipe( hostSwiftPackage: generatorOptions.hostSwiftPackagePath.map { - let hostTriple = try self.generatorOptions.deriveHostTriple() - return WebAssemblyRecipe.HostToolchainPackage(path: FilePath($0), triple: hostTriple) + let hostTriples = try self.generatorOptions.deriveHostTriples() + return WebAssemblyRecipe.HostToolchainPackage(path: FilePath($0), triples: hostTriples) }, targetSwiftPackagePath: FilePath(targetSwiftPackagePath), wasiSysroot: FilePath(self.wasiSysroot), diff --git a/Sources/SwiftSDKGenerator/Artifacts/DownloadableArtifacts.swift b/Sources/SwiftSDKGenerator/Artifacts/DownloadableArtifacts.swift index 1af7fd1..d5725ac 100644 --- a/Sources/SwiftSDKGenerator/Artifacts/DownloadableArtifacts.swift +++ b/Sources/SwiftSDKGenerator/Artifacts/DownloadableArtifacts.swift @@ -44,7 +44,7 @@ struct DownloadableArtifacts: Sendable { private let paths: PathsConfiguration init( - hostTriple: Triple, + hostTriples: [Triple], targetTriple: Triple, _ versions: VersionsConfiguration, _ paths: PathsConfiguration @@ -52,7 +52,22 @@ struct DownloadableArtifacts: Sendable { self.versions = versions self.paths = paths - if hostTriple.os == .linux { + guard let hostTriple = hostTriples.first else { + throw StringError("no host triples") + } + + if hostTriples.allSatisfy({ $0.os == .macosx }) { + self.hostSwift = .init( + remoteURL: versions.swiftDownloadURL( + subdirectory: "xcode", + platform: "osx", + fileExtension: "pkg" + ), + localPath: paths.artifactsCachePath + .appending("host_swift_\(versions.swiftVersion)_apple-macos.pkg"), + isPrebuilt: true + ) + } else if hostTriple.os == .linux { // Amazon Linux 2 is chosen for its best compatibility with all Swift-supported Linux hosts let hostArchSuffix = hostTriple.arch == .aarch64 ? "-\(Triple.Arch.aarch64.linuxConventionName)" : "" @@ -67,16 +82,13 @@ struct DownloadableArtifacts: Sendable { isPrebuilt: true ) } else { - self.hostSwift = .init( - remoteURL: versions.swiftDownloadURL( - subdirectory: "xcode", - platform: "osx", - fileExtension: "pkg" - ), - localPath: paths.artifactsCachePath - .appending("host_swift_\(versions.swiftVersion)_\(hostTriple.triple).pkg"), - isPrebuilt: true - ) + if hostTriples.count > 1 { + throw StringError( + "unsupported host triples \(hostTriples.map { "\($0)" }.joined(separator: ", ")) (only macOS supports multiple host triples)" + ) + } else { + throw StringError("unsupported host triple \(hostTriple)") + } } self.hostLLVM = .init( @@ -121,3 +133,10 @@ struct DownloadableArtifacts: Sendable { ) } } + +struct StringError: Error, CustomStringConvertible { + let description: String + init(_ description: String) { + self.description = description + } +} diff --git a/Sources/SwiftSDKGenerator/SwiftSDKRecipes/LinuxRecipe.swift b/Sources/SwiftSDKGenerator/SwiftSDKRecipes/LinuxRecipe.swift index af3ecc4..3849bdf 100644 --- a/Sources/SwiftSDKGenerator/SwiftSDKRecipes/LinuxRecipe.swift +++ b/Sources/SwiftSDKGenerator/SwiftSDKRecipes/LinuxRecipe.swift @@ -30,7 +30,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { } let mainTargetTriple: Triple - let mainHostTriple: Triple + let mainHostTriples: [Triple] let linuxDistribution: LinuxDistribution let targetSwiftSource: TargetSwiftSource let hostSwiftSource: HostSwiftSource @@ -46,7 +46,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { package init( targetTriple: Triple, - hostTriple: Triple, + hostTriples: [Triple], linuxDistribution: LinuxDistribution, swiftVersion: String, swiftBranch: String?, @@ -89,7 +89,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { self.init( mainTargetTriple: targetTriple, - mainHostTriple: hostTriple, + mainHostTriples: hostTriples, linuxDistribution: linuxDistribution, targetSwiftSource: targetSwiftSource, hostSwiftSource: hostSwiftSource, @@ -100,7 +100,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { package init( mainTargetTriple: Triple, - mainHostTriple: Triple, + mainHostTriples: [Triple], linuxDistribution: LinuxDistribution, targetSwiftSource: TargetSwiftSource, hostSwiftSource: HostSwiftSource, @@ -108,7 +108,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { logger: Logger ) { self.mainTargetTriple = mainTargetTriple - self.mainHostTriple = mainHostTriple + self.mainHostTriples = mainHostTriples self.linuxDistribution = linuxDistribution self.targetSwiftSource = targetSwiftSource self.hostSwiftSource = hostSwiftSource @@ -180,7 +180,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { func itemsToDownload(from artifacts: DownloadableArtifacts) -> [DownloadableArtifacts.Item] { var items: [DownloadableArtifacts.Item] = [] if self.hostSwiftSource != .preinstalled - && self.mainHostTriple.os != .linux + && !self.mainHostTriples.contains(where: { $0.os == .linux }) && !self.versionsConfiguration.swiftVersion.hasPrefix("6.") { items.append(artifacts.hostLLVM) @@ -218,7 +218,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { return nil } - return [self.mainHostTriple] + return self.mainHostTriples } package func makeSwiftSDK( @@ -240,7 +240,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { try await generator.createDirectoryIfNeeded(at: sdkDirPath) var downloadableArtifacts = try DownloadableArtifacts( - hostTriple: mainHostTriple, + hostTriples: mainHostTriples, targetTriple: generator.targetTriple, self.versionsConfiguration, generator.pathsConfiguration @@ -335,7 +335,7 @@ package struct LinuxRecipe: SwiftSDKRecipe { } if self.hostSwiftSource != .preinstalled { - if self.mainHostTriple.os != .linux + if !self.mainHostTriples.contains(where: { $0.os == .linux }) && !self.versionsConfiguration.swiftVersion.hasPrefix("6.") { try await generator.prepareLLDLinker(engine, llvmArtifact: downloadableArtifacts.hostLLVM) diff --git a/Sources/SwiftSDKGenerator/SwiftSDKRecipes/WebAssemblyRecipe.swift b/Sources/SwiftSDKGenerator/SwiftSDKRecipes/WebAssemblyRecipe.swift index 0e3d956..84996a6 100644 --- a/Sources/SwiftSDKGenerator/SwiftSDKRecipes/WebAssemblyRecipe.swift +++ b/Sources/SwiftSDKGenerator/SwiftSDKRecipes/WebAssemblyRecipe.swift @@ -24,11 +24,11 @@ package struct WebAssemblyRecipe: SwiftSDKRecipe { package struct HostToolchainPackage: Sendable { let path: FilePath - let triple: Triple + let triples: [Triple] - package init(path: FilePath, triple: Triple) { + package init(path: FilePath, triples: [Triple]) { self.path = path - self.triple = triple + self.triples = triples } } @@ -145,7 +145,7 @@ package struct WebAssemblyRecipe: SwiftSDKRecipe { logger.info("Copying Swift binaries for the host triple...") var hostTriples: [Triple]? = nil if let hostSwiftPackage { - hostTriples = [hostSwiftPackage.triple] + hostTriples = hostSwiftPackage.triples try await generator.rsync( from: hostSwiftPackage.path.appending("usr"), to: pathsConfiguration.toolchainDirPath diff --git a/Tests/SwiftSDKGeneratorTests/ArchitectureMappingTests.swift b/Tests/SwiftSDKGeneratorTests/ArchitectureMappingTests.swift index 893d8c7..ba76c11 100644 --- a/Tests/SwiftSDKGeneratorTests/ArchitectureMappingTests.swift +++ b/Tests/SwiftSDKGeneratorTests/ArchitectureMappingTests.swift @@ -49,7 +49,7 @@ final class ArchitectureMappingTests: XCTestCase { ) async throws { let recipe = try LinuxRecipe( targetTriple: targetTriple, - hostTriple: hostTriple, + hostTriples: [hostTriple], linuxDistribution: .ubuntu(.jammy), swiftVersion: "5.8-RELEASE", swiftBranch: nil, @@ -79,7 +79,7 @@ final class ArchitectureMappingTests: XCTestCase { // Verify download URLs let artifacts = try await DownloadableArtifacts( - hostTriple: hostTriple, + hostTriples: [hostTriple], targetTriple: sdk.targetTriple, recipe.versionsConfiguration, sdk.pathsConfiguration diff --git a/Tests/SwiftSDKGeneratorTests/SwiftSDKRecipes/LinuxRecipeTests.swift b/Tests/SwiftSDKGeneratorTests/SwiftSDKRecipes/LinuxRecipeTests.swift index b57807e..b948099 100644 --- a/Tests/SwiftSDKGeneratorTests/SwiftSDKRecipes/LinuxRecipeTests.swift +++ b/Tests/SwiftSDKGeneratorTests/SwiftSDKRecipes/LinuxRecipeTests.swift @@ -19,7 +19,7 @@ final class LinuxRecipeTests: XCTestCase { let logger = Logger(label: "LinuxRecipeTests") func createRecipe( - hostTriple: Triple = Triple("x86_64-unknown-linux-gnu"), + hostTriples: [Triple] = [Triple("x86_64-unknown-linux-gnu")], linuxDistribution: LinuxDistribution, swiftVersion: String = "6.0", withDocker: Bool = false, @@ -30,7 +30,7 @@ final class LinuxRecipeTests: XCTestCase { ) throws -> LinuxRecipe { try LinuxRecipe( targetTriple: Triple("aarch64-unknown-linux-gnu"), - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: swiftVersion, swiftBranch: nil, @@ -132,7 +132,7 @@ final class LinuxRecipeTests: XCTestCase { targetTriple: recipe.mainTargetTriple ) let downloadableArtifacts = try DownloadableArtifacts( - hostTriple: recipe.mainHostTriple, + hostTriples: recipe.mainHostTriples, targetTriple: recipe.mainTargetTriple, recipe.versionsConfiguration, pathsConfiguration @@ -154,8 +154,19 @@ final class LinuxRecipeTests: XCTestCase { XCTAssertEqual(foundHostSwift, includesHostSwift) } - func testItemsToDownloadForMacOSHost() throws { - let hostTriple = Triple("x86_64-apple-macos") + func testItemsToDownloadForMacOSHost_arm64() throws { + try _testItemsToDownloadForMacOSHost(hostTriples: [Triple("arm64-apple-macos")]) + } + + func testItemsToDownloadForMacOSHost_x86_64() throws { + try _testItemsToDownloadForMacOSHost(hostTriples: [Triple("x86_64-apple-macos")]) + } + + func testItemsToDownloadForMacOSHost_multiArch() throws { + try _testItemsToDownloadForMacOSHost(hostTriples: [Triple("arm64-apple-macos"), Triple("x86_64-apple-macos")]) + } + + func _testItemsToDownloadForMacOSHost(hostTriples: [Triple]) throws { let linuxDistribution = try LinuxDistribution(name: .ubuntu, version: "22.04") let testCases: [( @@ -165,7 +176,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Remote tarballs on Swift < 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.10" ), @@ -176,7 +187,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Remote tarballs on Swift >= 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "6.0" ), @@ -187,7 +198,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Remote target tarball with preinstalled toolchain recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.9", includeHostToolchain: false @@ -199,7 +210,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Local packages with Swift < 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.10", hostSwiftPackagePath: "/path/to/host/swift", @@ -212,7 +223,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Local packages with Swift >= 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "6.0", hostSwiftPackagePath: "/path/to/host/swift", @@ -235,13 +246,13 @@ final class LinuxRecipeTests: XCTestCase { } func testItemsToDownloadForLinuxHost() throws { - let hostTriple = Triple("x86_64-unknown-linux-gnu") + let hostTriples = [Triple("x86_64-unknown-linux-gnu")] let linuxDistribution = try LinuxDistribution(name: .ubuntu, version: "22.04") let testCases = [ ( // Remote tarballs on Swift < 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.10" ), @@ -251,7 +262,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Remote tarballs on Swift >= 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "6.0" ), @@ -261,7 +272,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Remote target tarball with preinstalled toolchain recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.9", includeHostToolchain: false @@ -272,7 +283,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Local packages with Swift < 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "5.10", hostSwiftPackagePath: "/path/to/host/swift", @@ -284,7 +295,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Local packages with Swift >= 6.0 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: linuxDistribution, swiftVersion: "6.0", hostSwiftPackagePath: "/path/to/host/swift", @@ -307,12 +318,12 @@ final class LinuxRecipeTests: XCTestCase { // Ubuntu toolchains will be selected for Debian 11 and 12 depending on the Swift version func testItemsToDownloadForDebianTargets() throws { - let hostTriple = Triple("x86_64-unknown-linux-gnu") + let hostTriples = [Triple("x86_64-unknown-linux-gnu")] let testCases = [ ( // Debian 11 -> ubuntu20.04 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "11"), swiftVersion: "5.9" ), @@ -321,7 +332,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Debian 12 with Swift 5.9 -> ubuntu22.04 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "12"), swiftVersion: "5.9" ), @@ -330,7 +341,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Debian 12 with Swift 5.10 -> ubuntu22.04 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "12"), swiftVersion: "5.10" ), @@ -339,7 +350,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Debian 11 with Swift 6.0 -> ubuntu20.04 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "11"), swiftVersion: "6.0" ), @@ -348,7 +359,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Debian 12 with Swift 5.10.1 -> debian12 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "12"), swiftVersion: "5.10.1" ), @@ -357,7 +368,7 @@ final class LinuxRecipeTests: XCTestCase { ( // Debian 12 with Swift 6.0 -> debian12 recipe: try createRecipe( - hostTriple: hostTriple, + hostTriples: hostTriples, linuxDistribution: try LinuxDistribution(name: .debian, version: "12"), swiftVersion: "6.0" ), @@ -373,7 +384,7 @@ final class LinuxRecipeTests: XCTestCase { targetTriple: testCase.recipe.mainTargetTriple ) let downloadableArtifacts = try DownloadableArtifacts( - hostTriple: testCase.recipe.mainHostTriple, + hostTriples: testCase.recipe.mainHostTriples, targetTriple: testCase.recipe.mainTargetTriple, testCase.recipe.versionsConfiguration, pathsConfiguration