Skip to content

Commit 4766a66

Browse files
committed
Add diagnostics for unsupported plugin dependency
Resolves rdar://95117424
1 parent 1c68725 commit 4766a66

File tree

3 files changed

+195
-3
lines changed

3 files changed

+195
-3
lines changed

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,8 +792,10 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
792792
let dependencies = try self.dependencies.map { dependency -> ResolvedTarget.Dependency in
793793
switch dependency {
794794
case .target(let targetBuilder, let conditions):
795+
try self.target.validateDependency(target: targetBuilder.target)
795796
return .target(try targetBuilder.construct(), conditions: conditions)
796797
case .product(let productBuilder, let conditions):
798+
try self.target.validateDependency(product: productBuilder.product, productPackage: productBuilder.packageBuilder.package.identity)
797799
let product = try productBuilder.construct()
798800
if !productBuilder.packageBuilder.isAllowedToVendUnsafeProducts {
799801
try self.diagnoseInvalidUseOfUnsafeFlags(product)
@@ -811,6 +813,19 @@ private final class ResolvedTargetBuilder: ResolvedBuilder<ResolvedTarget> {
811813
}
812814
}
813815

816+
extension Target {
817+
818+
func validateDependency(target: Target) throws {
819+
if self.type == .plugin && target.type == .library {
820+
throw PackageGraphError.unsupported(targetName: self.name, targetType: self.type.rawValue, dependencyName: target.name, dependencyType: target.type.rawValue, dependencyPackage: nil)
821+
}
822+
}
823+
func validateDependency(product: Product, productPackage: PackageIdentity) throws {
824+
if self.type == .plugin && product.type.isLibrary {
825+
throw PackageGraphError.unsupported(targetName: self.name, targetType: self.type.rawValue, dependencyName: product.name, dependencyType: product.type.description, dependencyPackage: productPackage.description)
826+
}
827+
}
828+
}
814829
/// Builder for resolved package.
815830
private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
816831

Sources/PackageGraph/PackageGraph.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ enum PackageGraphError: Swift.Error {
3636
targetName: String,
3737
packageIdentifier: String
3838
)
39-
39+
/// Dependency between a given target of its type and a dependent target/product of its type is unsupported
40+
case unsupported(targetName: String, targetType: String, dependencyName: String, dependencyType: String, dependencyPackage: String?)
4041
/// A product was found in multiple packages.
4142
case duplicateProduct(product: String, packages: [String])
4243

@@ -254,6 +255,12 @@ extension PackageGraphError: CustomStringConvertible {
254255
return "multiple aliases: ['\(aliases.joined(separator: "', '"))'] found for target '\(target)' in product '\(product)' from package '\(package)'"
255256
case .invalidSourcesForModuleAliasing(let target, let product, let package):
256257
return "module aliasing can only be used for Swift based targets; non-Swift sources found in target '\(target)' for product '\(product)' from package '\(package)'"
258+
case .unsupported(let targetName, let targetType, let dependencyName, let dependencyType, let dependencyPackage):
259+
var trailingMsg = ""
260+
if let depPkg = dependencyPackage {
261+
trailingMsg = " from package '\(depPkg)'"
262+
}
263+
return "target '\(targetName)' of type '\(targetType)' cannot depend on '\(dependencyName)' of type '\(dependencyType)'\(trailingMsg); this dependency is unsupported"
257264
}
258265
}
259266
}

Tests/SPMBuildCoreTests/PluginInvocationTests.swift

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Basics
14-
import PackageGraph
14+
@testable import PackageGraph
1515
import PackageLoading
1616
import PackageModel
1717
@testable import SPMBuildCore
@@ -584,6 +584,177 @@ class PluginInvocationTests: XCTestCase {
584584
}
585585
}
586586

587+
func testUnsupportedDependencyProduct() throws {
588+
try testWithTemporaryDirectory { tmpPath in
589+
// Create a sample package with a library product and a plugin.
590+
let packageDir = tmpPath.appending(components: "MyPackage")
591+
try localFileSystem.createDirectory(packageDir, recursive: true)
592+
try localFileSystem.writeFileContents(packageDir.appending(component: "Package.swift"), string: """
593+
// swift-tools-version: 5.7
594+
import PackageDescription
595+
let package = Package(
596+
name: "MyPackage",
597+
dependencies: [
598+
.package(path: "../FooPackage"),
599+
],
600+
targets: [
601+
.plugin(
602+
name: "MyPlugin",
603+
capability: .buildTool(),
604+
dependencies: [
605+
.product(name: "FooLib", package: "FooPackage"),
606+
]
607+
),
608+
]
609+
)
610+
""")
611+
612+
let myPluginTargetDir = packageDir.appending(components: "Plugins", "MyPlugin")
613+
try localFileSystem.createDirectory(myPluginTargetDir, recursive: true)
614+
try localFileSystem.writeFileContents(myPluginTargetDir.appending(component: "plugin.swift"), string: """
615+
import PackagePlugin
616+
import Foo
617+
@main struct MyBuildToolPlugin: BuildToolPlugin {
618+
func createBuildCommands(
619+
context: PluginContext,
620+
target: Target
621+
) throws -> [Command] { }
622+
}
623+
""")
624+
625+
let fooPkgDir = tmpPath.appending(components: "FooPackage")
626+
try localFileSystem.createDirectory(fooPkgDir, recursive: true)
627+
try localFileSystem.writeFileContents(fooPkgDir.appending(component: "Package.swift"), string: """
628+
// swift-tools-version: 5.7
629+
import PackageDescription
630+
let package = Package(
631+
name: "FooPackage",
632+
products: [
633+
.library(name: "FooLib",
634+
targets: ["Foo"]),
635+
],
636+
targets: [
637+
.target(
638+
name: "Foo",
639+
dependencies: []
640+
),
641+
]
642+
)
643+
""")
644+
let fooTargetDir = fooPkgDir.appending(components: "Sources", "Foo")
645+
try localFileSystem.createDirectory(fooTargetDir, recursive: true)
646+
try localFileSystem.writeFileContents(fooTargetDir.appending(component: "file.swift"), string: """
647+
public func foo() { }
648+
""")
649+
650+
// Load a workspace from the package.
651+
let observability = ObservabilitySystem.makeForTesting()
652+
let workspace = try Workspace(
653+
fileSystem: localFileSystem,
654+
forRootPackage: packageDir,
655+
customManifestLoader: ManifestLoader(toolchain: UserToolchain.default),
656+
delegate: MockWorkspaceDelegate()
657+
)
658+
659+
// Load the root manifest.
660+
let rootInput = PackageGraphRootInput(packages: [packageDir], dependencies: [])
661+
let rootManifests = try tsc_await {
662+
workspace.loadRootManifests(
663+
packages: rootInput.packages,
664+
observabilityScope: observability.topScope,
665+
completion: $0
666+
)
667+
}
668+
XCTAssert(rootManifests.count == 1, "\(rootManifests)")
669+
670+
// Load the package graph.
671+
XCTAssertThrowsError(try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope)) { error in
672+
var diagnosed = false
673+
if let realError = error as? PackageGraphError,
674+
realError.description == "plugin 'MyPlugin' cannot depend on 'FooLib' of type 'library' from package 'foopackage'; this dependency is unsupported" {
675+
diagnosed = true
676+
}
677+
XCTAssertTrue(diagnosed)
678+
}
679+
}
680+
}
681+
682+
func testUnsupportedDependencyTarget() throws {
683+
try testWithTemporaryDirectory { tmpPath in
684+
// Create a sample package with a library target and a plugin.
685+
let packageDir = tmpPath.appending(components: "MyPackage")
686+
try localFileSystem.createDirectory(packageDir, recursive: true)
687+
try localFileSystem.writeFileContents(packageDir.appending(component: "Package.swift"), string: """
688+
// swift-tools-version: 5.7
689+
import PackageDescription
690+
let package = Package(
691+
name: "MyPackage",
692+
targets: [
693+
.target(
694+
name: "MyLibrary",
695+
dependencies: []
696+
),
697+
.plugin(
698+
name: "MyPlugin",
699+
capability: .buildTool(),
700+
dependencies: [
701+
"MyLibrary"
702+
]
703+
),
704+
]
705+
)
706+
""")
707+
708+
let myLibraryTargetDir = packageDir.appending(components: "Sources", "MyLibrary")
709+
try localFileSystem.createDirectory(myLibraryTargetDir, recursive: true)
710+
try localFileSystem.writeFileContents(myLibraryTargetDir.appending(component: "library.swift"), string: """
711+
public func hello() { }
712+
""")
713+
let myPluginTargetDir = packageDir.appending(components: "Plugins", "MyPlugin")
714+
try localFileSystem.createDirectory(myPluginTargetDir, recursive: true)
715+
try localFileSystem.writeFileContents(myPluginTargetDir.appending(component: "plugin.swift"), string: """
716+
import PackagePlugin
717+
import MyLibrary
718+
@main struct MyBuildToolPlugin: BuildToolPlugin {
719+
func createBuildCommands(
720+
context: PluginContext,
721+
target: Target
722+
) throws -> [Command] { }
723+
}
724+
""")
725+
726+
// Load a workspace from the package.
727+
let observability = ObservabilitySystem.makeForTesting()
728+
let workspace = try Workspace(
729+
fileSystem: localFileSystem,
730+
forRootPackage: packageDir,
731+
customManifestLoader: ManifestLoader(toolchain: UserToolchain.default),
732+
delegate: MockWorkspaceDelegate()
733+
)
734+
735+
// Load the root manifest.
736+
let rootInput = PackageGraphRootInput(packages: [packageDir], dependencies: [])
737+
let rootManifests = try tsc_await {
738+
workspace.loadRootManifests(
739+
packages: rootInput.packages,
740+
observabilityScope: observability.topScope,
741+
completion: $0
742+
)
743+
}
744+
XCTAssert(rootManifests.count == 1, "\(rootManifests)")
745+
746+
// Load the package graph.
747+
XCTAssertThrowsError(try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope)) { error in
748+
var diagnosed = false
749+
if let realError = error as? PackageGraphError,
750+
realError.description == "plugin 'MyPlugin' cannot depend on 'MyLibrary' of type 'library'; this dependency is unsupported" {
751+
diagnosed = true
752+
}
753+
XCTAssertTrue(diagnosed)
754+
}
755+
}
756+
}
757+
587758
func testPrebuildPluginShouldNotUseExecTarget() throws {
588759
try testWithTemporaryDirectory { tmpPath in
589760
// Create a sample package with a library target and a plugin.
@@ -656,7 +827,6 @@ class PluginInvocationTests: XCTestCase {
656827
)
657828
]
658829
}
659-
660830
}
661831
""")
662832

0 commit comments

Comments
 (0)