Skip to content

Commit ca2fe64

Browse files
authoredJan 23, 2024
command plugins: Build command plugin dependencies for the host, not the target (#7280)
Always build command line plugin dependencies for the host triple. ### Motivation: Since #7164, dependencies of command plugins are once again being built for the _target_ rather than the host. This causes problem when cross compiling because the host needs to be able to run the plugin dependencies, but finds target binaries instead. This problem was fixed before in #6791 by forcing command plugin dependencies to be built for the host by overriding the default build parameters in swiftTool.createBuildSystem(). The same solution still works in this commit, but a better long-term option would be to rework BuildOperation.plan() to handle command plugin dependencies specially, as it already does for build plugin dependencies. ### Modifications: At present, BuildOperation.plan calls graph.invokeBuildToolPlugins to process sources. invokeBuildToolPlugins finds all build tool dependecies and builds them separately, using a specially-created BuildOperation instance: https://github.com/apple/swift-package-manager/blob/34efc0bfe9d40d9a019644ac8fcd0b852c491dfe/Sources/SPMBuildCore/Plugins/PluginInvocation.swift#L409 There is no equivalent step for command plugin dependencies, so they are built for the host architecture. Ideally we should rework BuildOperation.plan to build command and build plugin dependencies in the same way. This commit forces all plugin dependencies to be built for the host - this is similar to what was done in #6791 and #7273. ### Testing: An integration test checks that any targets depended on by a command plugin are built for the host, not for the target. * A new CommandPluginTestStub plugin has a dependency on a target executable which will be built automatically when the plugin is run. The test checks that the dependency is built for the host architecture, no matter which target architecture is selected using '--triple'. * The plugin also asks SwiftPM to build the 'placeholder' main target. The test checks that the dependency is built for the target architecture. The test is restricted to macOS because we can be sure of having a viable cross-compilation environment (arm64 to x86_64 and vice versa). The standard Linux build environments can't cross compile to other architectures. ### Result: Command plugins can be used again when cross-compiling.

File tree

6 files changed

+71
-1
lines changed

6 files changed

+71
-1
lines changed
 

‎Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Package.swift

+13
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,21 @@ let package = Package(
1919
description: "Build a target for testing"
2020
))
2121
),
22+
.plugin(
23+
name: "plugin-dependencies-stub",
24+
capability: .command(intent: .custom(
25+
verb: "build-plugin-dependency",
26+
description: "Build a plugin dependency for testing"
27+
)),
28+
dependencies: [
29+
.target(name: "plugintool")
30+
]
31+
),
2232
.executableTarget(
2333
name: "placeholder"
2434
),
35+
.executableTarget(
36+
name: "plugintool"
37+
),
2538
]
2639
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import PackagePlugin
2+
3+
@main
4+
struct test: CommandPlugin {
5+
// This plugin exists to test that the executable it requires is built correctly when cross-compiling
6+
func performCommand(context: PluginContext, arguments: [String]) async throws {
7+
print("Hello from dependencies-stub")
8+
let _ = try packageManager.build(
9+
.product("placeholder"),
10+
parameters: .init(configuration: .debug, logging: .concise)
11+
)
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("Hello from plugintool")

‎Sources/Commands/PackageTools/PluginCommand.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,11 @@ struct PluginCommand: SwiftCommand {
319319
// Build or bring up-to-date any executable host-side tools on which this plugin depends. Add them and any binary dependencies to the tool-names-to-path map.
320320
let buildSystem = try swiftTool.createBuildSystem(
321321
explicitBuildSystem: .native,
322-
cacheBuildManifest: false
322+
cacheBuildManifest: false,
323+
// Force all dependencies to be built for the host, to work around the fact that BuildOperation.plan
324+
// knows to compile build tool plugin dependencies for the host but does not do the same for command
325+
// plugins.
326+
productsBuildParameters: buildParameters
323327
)
324328
let accessibleTools = try plugin.processAccessibleTools(
325329
packageGraph: packageGraph,

‎Tests/BuildTests/PluginsBuildPlanTests.swift

+39
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
import Basics
1414
import SPMTestSupport
15+
@testable import SPMBuildCore
1516
import XCTest
17+
import PackageModel
1618

1719
final class PluginsBuildPlanTests: XCTestCase {
1820
func testBuildToolsDatabasePath() throws {
@@ -22,4 +24,41 @@ final class PluginsBuildPlanTests: XCTestCase {
2224
XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/plugins/tools/build.db"))))
2325
}
2426
}
27+
28+
func testCommandPluginDependenciesWhenCrossCompiling() throws {
29+
// Command Plugin dependencies must be built for the host.
30+
// This test is only supported on macOS because that is the only
31+
// platform on which we can currently be sure of having a viable
32+
// cross-compilation environment (arm64->x86_64 or vice versa).
33+
// On Linux it is typically only possible to build for the host
34+
// environment unless cross-compilation SDKs are being used.
35+
#if !os(macOS)
36+
try XCTSkipIf(true, "test is only supported on macOS")
37+
#endif
38+
39+
let hostToolchain = try UserToolchain(swiftSDK: .hostSwiftSDK())
40+
let hostTriple = try! hostToolchain.targetTriple.withoutVersion().tripleString
41+
42+
let x86Triple = "x86_64-apple-macosx"
43+
let armTriple = "arm64-apple-macosx"
44+
let targetTriple = hostToolchain.targetTriple.arch == .aarch64 ? x86Triple : armTriple
45+
46+
// By default, plugin dependencies are built for the host platform
47+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
48+
let (stdout, stderr) = try executeSwiftPackage(fixturePath, extraArgs: ["-v", "build-plugin-dependency"])
49+
XCTAssertMatch(stdout, .contains("Hello from dependencies-stub"))
50+
XCTAssertMatch(stderr, .contains("Build of product 'plugintool' complete!"))
51+
XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool"))))
52+
XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/placeholder"))))
53+
}
54+
55+
// When cross compiling the final product, plugin dependencies should still be built for the host
56+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
57+
let (stdout, stderr) = try executeSwiftPackage(fixturePath, extraArgs: ["--triple", targetTriple, "-v", "build-plugin-dependency"])
58+
XCTAssertMatch(stdout, .contains("Hello from dependencies-stub"))
59+
XCTAssertMatch(stderr, .contains("Build of product 'plugintool' complete!"))
60+
XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool"))))
61+
XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(targetTriple)/debug/placeholder"))))
62+
}
63+
}
2564
}

0 commit comments

Comments
 (0)
Please sign in to comment.