diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 4fea92bedb8..e809052aee0 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -927,12 +927,6 @@ public final class SwiftCommandState { private lazy var _targetToolchain: Result = { let swiftSDK: SwiftSDK let hostSwiftSDK: SwiftSDK - let store = SwiftSDKBundleStore( - swiftSDKsDirectory: self.sharedSwiftSDKsDirectory, - fileSystem: self.fileSystem, - observabilityScope: self.observabilityScope, - outputHandler: { print($0.description) } - ) do { let hostToolchain = try _hostToolchain.get() hostSwiftSDK = hostToolchain.swiftSDK @@ -942,6 +936,15 @@ public final class SwiftCommandState { warning: "`--experimental-swift-sdk` is deprecated and will be removed in a future version of SwiftPM. Use `--swift-sdk` instead." ) } + + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: self.sharedSwiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope, + outputHandler: { print($0.description) } + ) + swiftSDK = try SwiftSDK.deriveTargetSwiftSDK( hostSwiftSDK: hostSwiftSDK, hostTriple: hostToolchain.targetTriple, diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index aacfad57428..cdcdfd781f4 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -730,6 +730,7 @@ public struct SwiftSDK: Equatable { if let customDestination = customCompileDestination { let swiftSDKs = try SwiftSDK.decode( fromFile: customDestination, + hostToolchainBinDir: store.hostToolchainBinDir, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -853,6 +854,7 @@ extension SwiftSDK { /// Load a ``SwiftSDK`` description from a JSON representation from disk. public static func decode( fromFile path: Basics.AbsolutePath, + hostToolchainBinDir: Basics.AbsolutePath, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> [SwiftSDK] { @@ -862,6 +864,7 @@ extension SwiftSDK { return try Self.decode( semanticVersion: version, fromFile: path, + hostToolchainBinDir: hostToolchainBinDir, fileSystem: fileSystem, decoder: decoder, observabilityScope: observabilityScope @@ -876,10 +879,16 @@ extension SwiftSDK { private static func decode( semanticVersion: SemanticVersionInfo, fromFile path: Basics.AbsolutePath, + hostToolchainBinDir: Basics.AbsolutePath, fileSystem: FileSystem, decoder: JSONDecoder, observabilityScope: ObservabilityScope ) throws -> [SwiftSDK] { + let wasmKitProperties = Toolset.ToolProperties( + path: hostToolchainBinDir.appending("wasmkit"), + extraCLIOptions: ["run"] + ) + switch semanticVersion.schemaVersion { case Version(3, 0, 0): let swiftSDKs = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV3.self) @@ -889,7 +898,12 @@ extension SwiftSDK { let triple = try Triple(triple) let pathStrings = properties.toolsetPaths ?? [] - let toolset = try pathStrings.reduce(into: Toolset(knownTools: [:], rootPaths: [])) { + let defaultTools: [Toolset.KnownTool: Toolset.ToolProperties] = if triple.isWasm { + [.debugger: wasmKitProperties, .testRunner: wasmKitProperties] + } else { + [:] + } + let toolset = try pathStrings.reduce(into: Toolset(knownTools: defaultTools, rootPaths: [])) { try $0.merge( with: Toolset( from: .init(validating: $1, relativeTo: swiftSDKDirectory), @@ -914,8 +928,13 @@ extension SwiftSDK { return try swiftSDKs.targetTriples.map { triple, properties in let triple = try Triple(triple) + let defaultTools: [Toolset.KnownTool: Toolset.ToolProperties] = if triple.isWasm { + [.debugger: wasmKitProperties, .testRunner: wasmKitProperties] + } else { + [:] + } let pathStrings = properties.toolsetPaths ?? [] - let toolset = try pathStrings.reduce(into: Toolset(knownTools: [:], rootPaths: [])) { + let toolset = try pathStrings.reduce(into: Toolset(knownTools: defaultTools, rootPaths: [])) { try $0.merge( with: Toolset( from: .init(validating: $1, relativeTo: swiftSDKDirectory), diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift index 06ef11f865c..66bfef317e6 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift @@ -63,6 +63,9 @@ public final class SwiftSDKBundleStore { /// Directory in which Swift SDKs bundles are stored. let swiftSDKsDirectory: AbsolutePath + /// `usr/bin` directory of the "root" toolchain that includes this currently running SwiftPM instance. + let hostToolchainBinDir: AbsolutePath + /// File system instance used for reading from and writing to SDK bundles stored on it. let fileSystem: any FileSystem @@ -77,12 +80,14 @@ public final class SwiftSDKBundleStore { public init( swiftSDKsDirectory: AbsolutePath, + hostToolchainBinDir: AbsolutePath, fileSystem: any FileSystem, observabilityScope: ObservabilityScope, outputHandler: @escaping (Output) -> Void, downloadProgressAnimation: ProgressAnimationProtocol? = nil ) { self.swiftSDKsDirectory = swiftSDKsDirectory + self.hostToolchainBinDir = hostToolchainBinDir self.fileSystem = fileSystem self.observabilityScope = observabilityScope self.outputHandler = outputHandler @@ -394,7 +399,9 @@ public final class SwiftSDKBundleStore { do { let swiftSDKs = try SwiftSDK.decode( - fromFile: variantConfigurationPath, fileSystem: fileSystem, + fromFile: variantConfigurationPath, + hostToolchainBinDir: self.hostToolchainBinDir, + fileSystem: fileSystem, observabilityScope: observabilityScope ) diff --git a/Sources/PackageModel/Toolset.swift b/Sources/PackageModel/Toolset.swift index 092532228ab..09ce03e4168 100644 --- a/Sources/PackageModel/Toolset.swift +++ b/Sources/PackageModel/Toolset.swift @@ -34,7 +34,7 @@ public struct Toolset: Equatable { /// Properties of a known tool in a ``Toolset``. public struct ToolProperties: Equatable { /// Absolute path to the tool on the filesystem. If absent, implies a default tool is used. - public fileprivate(set) var path: AbsolutePath? + public internal(set) var path: AbsolutePath? /// Command-line options to be passed to the tool when it's invoked. public internal(set) var extraCLIOptions: [String] diff --git a/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift b/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift index 2c7098a02f8..2d7bda720f9 100644 --- a/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift +++ b/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift @@ -45,6 +45,7 @@ protocol ConfigurationSubcommand: SwiftSDKSubcommand { extension ConfigurationSubcommand { func run( hostTriple: Triple, + hostToolchain: UserToolchain, _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { @@ -52,6 +53,7 @@ extension ConfigurationSubcommand { let bundleStore = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, fileSystem: self.fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0) } diff --git a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift index 3708bf7eba9..63503962043 100644 --- a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift @@ -123,6 +123,7 @@ struct ConfigureSwiftSDK: AsyncParsableCommand { do { let bundleStore = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, fileSystem: self.fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0) } diff --git a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift index d1ce17f6beb..21fcd8f4beb 100644 --- a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift @@ -50,6 +50,7 @@ struct InstallSwiftSDK: SwiftSDKSubcommand { func run( hostTriple: Triple, + hostToolchain: UserToolchain, _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws { @@ -58,6 +59,7 @@ struct InstallSwiftSDK: SwiftSDKSubcommand { let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, fileSystem: self.fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0.description) }, diff --git a/Sources/SwiftSDKCommand/ListSwiftSDKs.swift b/Sources/SwiftSDKCommand/ListSwiftSDKs.swift index 062b43b9e75..9e19b471195 100644 --- a/Sources/SwiftSDKCommand/ListSwiftSDKs.swift +++ b/Sources/SwiftSDKCommand/ListSwiftSDKs.swift @@ -32,11 +32,13 @@ package struct ListSwiftSDKs: SwiftSDKSubcommand { func run( hostTriple: Triple, + hostToolchain: UserToolchain, _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, fileSystem: fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0.description) } diff --git a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift index acb75dff6a3..5e8c61530ce 100644 --- a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift @@ -34,6 +34,7 @@ package struct RemoveSwiftSDK: SwiftSDKSubcommand { func run( hostTriple: Triple, + hostToolchain: UserToolchain, _ swiftSDKsDirectory: Basics.AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws { @@ -47,6 +48,7 @@ package struct RemoveSwiftSDK: SwiftSDKSubcommand { } else { let bundleStore = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchain.swiftCompilerPath.parentDirectory, fileSystem: self.fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0) } diff --git a/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift index 6b1c146711b..c1a62ad35a8 100644 --- a/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift @@ -30,6 +30,7 @@ protocol SwiftSDKSubcommand: AsyncParsableCommand { /// - observabilityScope: observability scope used for logging. func run( hostTriple: Triple, + hostToolchain: UserToolchain, _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws @@ -71,7 +72,7 @@ extension SwiftSDKSubcommand { var commandError: Error? = nil do { - try await self.run(hostTriple: triple, swiftSDKsDirectory, observabilityScope) + try await self.run(hostTriple: triple, hostToolchain: hostToolchain, swiftSDKsDirectory, observabilityScope) if observabilityScope.errorsReported { throw ExitCode.failure } diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index 5a69093e5cf..a65ed0df18a 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -186,6 +186,7 @@ final class SwiftSDKBundleTests: XCTestCase { var output = [SwiftSDKBundleStore.Output]() let store = SwiftSDKBundleStore( swiftSDKsDirectory: tmpDir, + hostToolchainBinDir: tmpDir, fileSystem: localFileSystem, observabilityScope: observabilityScope, outputHandler: { @@ -227,6 +228,7 @@ final class SwiftSDKBundleTests: XCTestCase { var output = [SwiftSDKBundleStore.Output]() let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: "/tmp", fileSystem: fileSystem, observabilityScope: system.topScope, outputHandler: { @@ -319,6 +321,7 @@ final class SwiftSDKBundleTests: XCTestCase { var output = [SwiftSDKBundleStore.Output]() let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: "/tmp", fileSystem: fileSystem, observabilityScope: system.topScope, outputHandler: { @@ -359,6 +362,7 @@ final class SwiftSDKBundleTests: XCTestCase { var output = [SwiftSDKBundleStore.Output]() let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: "/tmp", fileSystem: fileSystem, observabilityScope: system.topScope, outputHandler: { @@ -400,9 +404,11 @@ final class SwiftSDKBundleTests: XCTestCase { let system = ObservabilitySystem.makeForTesting() let hostSwiftSDK = try SwiftSDK.hostSwiftSDK(environment: [:]) let hostTriple = try! Triple("arm64-apple-macosx14.0") + let hostToolchainBinDir = AbsolutePath("/tmp") let archiver = MockArchiver() let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: hostToolchainBinDir, fileSystem: fileSystem, observabilityScope: system.topScope, outputHandler: { _ in } @@ -518,6 +524,7 @@ final class SwiftSDKBundleTests: XCTestCase { var output = [SwiftSDKBundleStore.Output]() let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: "/tmp", fileSystem: fileSystem, observabilityScope: system.topScope, outputHandler: { output.append($0) } diff --git a/Tests/PackageModelTests/SwiftSDKTests.swift b/Tests/PackageModelTests/SwiftSDKTests.swift index 2d11360b9ea..7f7a5dd9650 100644 --- a/Tests/PackageModelTests/SwiftSDKTests.swift +++ b/Tests/PackageModelTests/SwiftSDKTests.swift @@ -25,6 +25,7 @@ private let hostTriple = try! Triple("arm64-apple-darwin22.1.0") private let olderHostTriple = try! Triple("arm64-apple-darwin20.1.0") private let linuxGNUTargetTriple = try! Triple("x86_64-unknown-linux-gnu") private let linuxMuslTargetTriple = try! Triple("x86_64-unknown-linux-musl") +private let wasiTargetTriple = try! Triple("wasm32-unknown-wasi") private let extraFlags = BuildFlags( cCompilerFlags: ["-fintegrated-as"], cxxCompilerFlags: ["-fno-exceptions"], @@ -139,6 +140,22 @@ private let invalidToolsetDestinationV3 = ( """# as SerializedJSON ) + +private let wasiWithoutToolsetsSwiftSDKv4 = ( + path: bundleRootPath.appending(component: "wasiSwiftSDKv4.json"), + json: #""" + { + "targetTriples": { + "\#(wasiTargetTriple.tripleString)": { + "sdkRootPath": "\#(sdkRootDir)", + "toolsetPaths": [] + } + }, + "schemaVersion": "4.0" + } + """# as SerializedJSON +) + private let toolsetNoRootSwiftSDKv4 = ( path: bundleRootPath.appending(component: "toolsetNoRootSwiftSDKv4.json"), json: #""" @@ -347,12 +364,13 @@ private let testFiles: [(path: AbsolutePath, json: SerializedJSON)] = [ missingToolsetSwiftSDKv4, invalidVersionSwiftSDKv4, invalidToolsetSwiftSDKv4, + wasiWithoutToolsetsSwiftSDKv4, otherToolsNoRoot, someToolsWithRoot, invalidToolset, ] -final class DestinationTests: XCTestCase { +final class SwiftSDKTests: XCTestCase { func testDestinationCodable() throws { let fs = InMemoryFileSystem() try fs.createDirectory(AbsolutePath(validating: "/tools")) @@ -367,6 +385,7 @@ final class DestinationTests: XCTestCase { let destinationV1Decoded = try SwiftSDK.decode( fromFile: destinationV1.path, + hostToolchainBinDir: bundleRootPath.appending(toolchainBinDir), fileSystem: fs, observabilityScope: observability ) @@ -389,6 +408,7 @@ final class DestinationTests: XCTestCase { let destinationV2Decoded = try SwiftSDK.decode( fromFile: destinationV2.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability ) @@ -397,6 +417,7 @@ final class DestinationTests: XCTestCase { let toolsetNoRootDestinationV3Decoded = try SwiftSDK.decode( fromFile: toolsetNoRootDestinationV3.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability ) @@ -405,6 +426,7 @@ final class DestinationTests: XCTestCase { let toolsetRootDestinationV3Decoded = try SwiftSDK.decode( fromFile: toolsetRootDestinationV3.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability ) @@ -413,6 +435,7 @@ final class DestinationTests: XCTestCase { XCTAssertThrowsError(try SwiftSDK.decode( fromFile: missingToolsetDestinationV3.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability )) { @@ -429,12 +452,14 @@ final class DestinationTests: XCTestCase { } XCTAssertThrowsError(try SwiftSDK.decode( fromFile: invalidVersionDestinationV3.path, + hostToolchainBinDir: bundleRootPath.appending(toolchainBinDir), fileSystem: fs, observabilityScope: observability )) XCTAssertThrowsError(try SwiftSDK.decode( fromFile: invalidToolsetDestinationV3.path, + hostToolchainBinDir: bundleRootPath.appending(toolchainBinDir), fileSystem: fs, observabilityScope: observability )) { @@ -447,6 +472,7 @@ final class DestinationTests: XCTestCase { let toolsetNoRootSwiftSDKv4Decoded = try SwiftSDK.decode( fromFile: toolsetNoRootSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability ) @@ -455,6 +481,7 @@ final class DestinationTests: XCTestCase { let toolsetRootSwiftSDKv4Decoded = try SwiftSDK.decode( fromFile: toolsetRootSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability ) @@ -463,6 +490,7 @@ final class DestinationTests: XCTestCase { XCTAssertThrowsError(try SwiftSDK.decode( fromFile: missingToolsetSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability )) { @@ -479,12 +507,14 @@ final class DestinationTests: XCTestCase { } XCTAssertThrowsError(try SwiftSDK.decode( fromFile: invalidVersionSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability )) XCTAssertThrowsError(try SwiftSDK.decode( fromFile: invalidToolsetSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, fileSystem: fs, observabilityScope: observability )) { @@ -494,6 +524,23 @@ final class DestinationTests: XCTestCase { .hasPrefix("Couldn't parse toolset configuration at `\(toolsetDefinition)`: ") ?? false ) } + + let wasiWithoutToolsetsDecoded = try SwiftSDK.decode( + fromFile: wasiWithoutToolsetsSwiftSDKv4.path, + hostToolchainBinDir: toolchainBinAbsolutePath, + fileSystem: fs, + observabilityScope: observability + ) + + XCTAssertEqual(wasiWithoutToolsetsDecoded.count, 1) + + let wasmKitProperties = Toolset.ToolProperties( + path: toolchainBinAbsolutePath.appending("wasmkit"), + extraCLIOptions: ["run"] + ) + XCTAssertEqual(wasiWithoutToolsetsDecoded[0].toolset.knownTools[.debugger], wasmKitProperties) + + XCTAssertEqual(wasiWithoutToolsetsDecoded[0].toolset.knownTools[.testRunner], wasmKitProperties) } func testSelectDestination() throws { diff --git a/Tests/PackageModelTests/ToolsetTests.swift b/Tests/PackageModelTests/ToolsetTests.swift index 9efb1928194..d0568e4127a 100644 --- a/Tests/PackageModelTests/ToolsetTests.swift +++ b/Tests/PackageModelTests/ToolsetTests.swift @@ -222,6 +222,7 @@ final class ToolsetTests: XCTestCase { let store = SwiftSDKBundleStore( swiftSDKsDirectory: "/", + hostToolchainBinDir: usrBinTools[.swiftCompiler]!.parentDirectory, fileSystem: fileSystem, observabilityScope: observability.topScope, outputHandler: { _ in }