Skip to content

Don't link plugin dependencies to products #6695

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// swift-tools-version:5.9
import PackageDescription

let package = Package(
name: "111920845-sample",
platforms: [
.macOS(.v10_15), // example uses swift concurrency which is only available in 10.15 or newer
],
products: [
.executable(name: "MyPluginExecutable", targets: ["MyPluginExecutable"]),
.plugin(name: "MyPlugin", targets: ["MyPlugin"]),
],
targets: [
.executableTarget(name: "MyPluginExecutable"),
.plugin(name: "MyPlugin", capability: .buildTool, dependencies: ["MyPluginExecutable"]),

.target(name: "MyLibrary", plugins: ["MyPlugin"]),
.executableTarget(name: "MyExecutable", dependencies: ["MyLibrary"]),
.testTarget(name: "MyExecutableTests", dependencies: ["MyExecutable"]),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import PackagePlugin

@main
struct MyPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
return [.buildCommand(
displayName: "Running MyPluginExecutable",
executable: try context.tool(named: "MyPluginExecutable").path,
arguments: [],
environment: [:],
inputFiles: [],
outputFiles: []
)]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import MyLibrary

@main
struct MyExecutable {
static func main() async throws {
print("Hello from executable.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@main
struct MyExecutable {
static func main() async throws {
print("Hello from plugin executable.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import XCTest
@testable import MyExecutable

final class MyExecutableTests: XCTestCase {
func test() throws {
XCTAssertTrue(true)
}
}
23 changes: 20 additions & 3 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -695,25 +695,42 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
libraryBinaryPaths: Set<AbsolutePath>,
availableTools: [String: AbsolutePath]
) {
/* Prior to tools-version 5.9, we used to errorneously recursively traverse plugin dependencies and statically include their
targets. For compatibility reasons, we preserve that behavior for older tools-versions. */
let shouldExcludePlugins: Bool
if let toolsVersion = self.graph.package(for: product)?.manifest.toolsVersion {
shouldExcludePlugins = toolsVersion >= .v5_9
} else {
shouldExcludePlugins = false
}

// Sort the product targets in topological order.
let nodes: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) }
let allTargets = try topologicalSort(nodes, successors: { dependency in
switch dependency {
// Include all the dependencies of a target.
case .target(let target, _):
if target.type == .macro {
return []
}
if shouldExcludePlugins, target.type == .plugin {
return []
}
return target.dependencies.filter { $0.satisfies(self.buildEnvironment) }

// For a product dependency, we only include its content only if we
// need to statically link it or if it's a plugin.
// need to statically link it.
case .product(let product, _):
guard dependency.satisfies(self.buildEnvironment) else {
return []
}

let productDependencies: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) }
switch product.type {
case .library(.automatic), .library(.static), .plugin:
return product.targets.map { .target($0, conditions: []) }
case .library(.automatic), .library(.static):
return productDependencies
case .plugin:
return shouldExcludePlugins ? [] : productDependencies
case .library(.dynamic), .test, .executable, .snippet, .macro:
return []
}
Expand Down
9 changes: 9 additions & 0 deletions Tests/FunctionalTests/PluginTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,15 @@ class PluginTests: XCTestCase {
}
}

func testIncorrectDependencies() throws {
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")

try fixture(name: "Miscellaneous/Plugins") { path in
let (stdout, stderr) = try executeSwiftBuild(path.appending("IncorrectDependencies"), extraArgs: ["--build-tests"])
XCTAssert(stdout.contains("Build complete!"), "output:\n\(stderr)\n\(stdout)")
}
}

func testSandboxViolatingBuildToolPluginCommands() throws {
#if !os(macOS)
try XCTSkipIf(true, "sandboxing tests are only supported on macOS")
Expand Down