diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift index 2e0d01e01cb..7fd995a482f 100644 --- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift +++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift @@ -74,13 +74,14 @@ extension BinaryTarget { // Filter supported triples with versionLessTriple and pass into // ExecutableInfo; empty if non matching triples found. try entry.value.variants.map{ - ExecutableInfo(name: entry.key, executablePath: try AbsolutePath(validating: $0.path, relativeTo: self.artifactPath), supportedTriples: $0.supportedTriples.filter({$0 == versionLessTriple})) + let filteredSupportedTriples = try $0.supportedTriples.filter({try $0.withoutVersion() == versionLessTriple}) + return ExecutableInfo(name: entry.key, executablePath: try AbsolutePath(validating: $0.path, relativeTo: self.artifactPath), supportedTriples: filteredSupportedTriples) } } } } -fileprivate extension Triple { +extension Triple { func withoutVersion() throws -> Triple { if isDarwin() { let stringWithoutVersion = tripleString(forPlatformVersion: "") diff --git a/Sources/SPMBuildCore/PluginInvocation.swift b/Sources/SPMBuildCore/PluginInvocation.swift index 6b8f54e895e..5dc267506e1 100644 --- a/Sources/SPMBuildCore/PluginInvocation.swift +++ b/Sources/SPMBuildCore/PluginInvocation.swift @@ -372,8 +372,8 @@ extension PackageGraph { let toolNamesToTriples = accessibleTools.reduce(into: [String: [String]](), { dict, tool in switch tool { - case .vendedTool(let name, _, let triple): - dict[name] = triple + case .vendedTool(let name, _, let triples): + dict[name, default: []].append(contentsOf: triples) default: break } }) @@ -533,7 +533,7 @@ public extension PluginTarget { if let target = executableOrBinaryTarget as? BinaryTarget { // TODO: Memoize this result for the host triple let execInfos = try target.parseArtifactArchives(for: hostTriple, fileSystem: fileSystem) - return execInfos.map{ .vendedTool(name: $0.name, path: $0.executablePath, supportedTriples: $0.supportedTriples.map{$0.tripleString}) } + return try execInfos.map{ .vendedTool(name: $0.name, path: $0.executablePath, supportedTriples: try $0.supportedTriples.map{ try $0.withoutVersion().tripleString }) } } // For an executable target we create a `builtTool`. else if executableOrBinaryTarget.type == .executable { diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index b9aecf5a2c6..d866c048a78 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -1056,8 +1056,7 @@ class PluginInvocationTests: XCTestCase { } } - - func testParseArtifactNotSupportedOnTargetPlatform() throws { + func checkParseArtifactsPlatformCompatibility(artifactSupportedTriples: [Triple], hostTriple: Triple, pluginResultChecker: ([ResolvedTarget: [BuildToolPluginInvocationResult]]) throws -> ()) throws { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -1119,7 +1118,12 @@ class PluginInvocationTests: XCTestCase { } """ try localFileSystem.writeFileContents(myPluginTargetDir.appending(component: "plugin.swift"), string: content) - let tripleString = "x86_64-apple-macos15" + let artifactVariants = artifactSupportedTriples.map { + """ + { "path": "LocalBinaryTool.sh", "supportedTriples": ["\($0.tripleString)"] } + """ + } + try localFileSystem.writeFileContents(packageDir.appending(components: "Binaries", "LocalBinaryTool.artifactbundle", "info.json")) { $0 <<< """ { "schemaVersion": "1.0", @@ -1128,9 +1132,7 @@ class PluginInvocationTests: XCTestCase { "type": "executable", "version": "1.2.3", "variants": [ - { "path": "LocalBinaryTool.sh", - "supportedTriples": ["\(tripleString)"] - }, + \(artifactVariants.joined(separator: ",")) ] } } @@ -1166,12 +1168,16 @@ class PluginInvocationTests: XCTestCase { XCTAssertEqual(buildToolPlugin.name, "Foo") XCTAssertEqual(buildToolPlugin.capability, .buildTool) + // Construct a toolchain with a made-up host/target triple + let destination = try Destination.default + let toolchain = try UserToolchain(destination: Destination(hostTriple: hostTriple, targetTriple: hostTriple, sdkRootDir: destination.sdkRootDir, toolchainBinDir: destination.toolchainBinDir)) + // Create a plugin script runner for the duration of the test. let pluginCacheDir = tmpPath.appending(component: "plugin-cache") let pluginScriptRunner = DefaultPluginScriptRunner( fileSystem: localFileSystem, cacheDir: pluginCacheDir, - toolchain: try UserToolchain.default + toolchain: toolchain ) // Invoke build tool plugin @@ -1186,13 +1192,54 @@ class PluginInvocationTests: XCTestCase { observabilityScope: observability.topScope, fileSystem: localFileSystem ) - var checked = false + try pluginResultChecker(result) + } + } + + func testParseArtifactNotSupportedOnTargetPlatform() throws { + let hostTriple = try UserToolchain.default.triple + let artifactSupportedTriples = try [Triple("riscv64-apple-windows-android")] + + var checked = false + try checkParseArtifactsPlatformCompatibility(artifactSupportedTriples: artifactSupportedTriples, hostTriple: hostTriple) { result in if let pluginResult = result.first, let diag = pluginResult.value.first?.diagnostics, diag.description == "[[error]: Tool ‘LocalBinaryTool’ is not supported on the target platform]" { checked = true } - XCTAssertTrue(checked) + } + XCTAssertTrue(checked) + } + + func testParseArtifactsDoesNotCheckPlatformVersion() throws { + #if !os(macOS) + throw XCTSkip("platform versions are only available if the host is macOS") + #else + let hostTriple = try UserToolchain.default.triple + let artifactSupportedTriples = try [Triple("\(hostTriple.withoutVersion().tripleString)20.0")] + + try checkParseArtifactsPlatformCompatibility(artifactSupportedTriples: artifactSupportedTriples, hostTriple: hostTriple) { result in + result.forEach { + $0.value.forEach { + XCTAssertTrue($0.succeeded, "plugin unexpectedly failed") + XCTAssertEqual($0.diagnostics.map { $0.message }, [], "plugin produced unexpected diagnostics") + } + } + } + #endif + } + + func testParseArtifactsConsidersAllSupportedTriples() throws { + let hostTriple = try UserToolchain.default.triple + let artifactSupportedTriples = [hostTriple, try Triple("riscv64-apple-windows-android")] + + try checkParseArtifactsPlatformCompatibility(artifactSupportedTriples: artifactSupportedTriples, hostTriple: hostTriple) { result in + result.forEach { + $0.value.forEach { + XCTAssertTrue($0.succeeded, "plugin unexpectedly failed") + XCTAssertEqual($0.diagnostics.map { $0.message }, [], "plugin produced unexpected diagnostics") + } + } } } }