@@ -579,4 +579,337 @@ class PluginInvocationTests: XCTestCase {
579
579
}
580
580
}
581
581
}
582
+
583
+ func testUnsupportedDependencyProduct( ) throws {
584
+ try testWithTemporaryDirectory { tmpPath in
585
+ // Create a sample package with a library target and a plugin.
586
+ let packageDir = tmpPath. appending ( components: " MyPackage " )
587
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
588
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
589
+ // swift-tools-version: 5.7
590
+ import PackageDescription
591
+ let package = Package(
592
+ name: " MyPackage " ,
593
+ dependencies: [
594
+ .package(path: " ../FooPackage " ),
595
+ ],
596
+ targets: [
597
+ .plugin(
598
+ name: " MyPlugin " ,
599
+ capability: .buildTool(),
600
+ dependencies: [
601
+ .product(name: " FooLib " , package: " FooPackage " ),
602
+ ]
603
+ ),
604
+ ]
605
+ )
606
+ """ )
607
+
608
+ let myPluginTargetDir = packageDir. appending ( components: " Plugins " , " MyPlugin " )
609
+ try localFileSystem. createDirectory ( myPluginTargetDir, recursive: true )
610
+ try localFileSystem. writeFileContents ( myPluginTargetDir. appending ( component: " plugin.swift " ) , string: """
611
+ import PackagePlugin
612
+ import Foo
613
+ @main struct MyBuildToolPlugin: BuildToolPlugin {
614
+ func createBuildCommands(
615
+ context: PluginContext,
616
+ target: Target
617
+ ) throws -> [Command] { }
618
+ }
619
+ """ )
620
+
621
+ let fooPkgDir = tmpPath. appending ( components: " FooPackage " )
622
+ try localFileSystem. createDirectory ( fooPkgDir, recursive: true )
623
+ try localFileSystem. writeFileContents ( fooPkgDir. appending ( component: " Package.swift " ) , string: """
624
+ // swift-tools-version: 5.7
625
+ import PackageDescription
626
+ let package = Package(
627
+ name: " FooPackage " ,
628
+ products: [
629
+ .library(name: " FooLib " ,
630
+ targets: [ " Foo " ]),
631
+ ],
632
+ targets: [
633
+ .target(
634
+ name: " Foo " ,
635
+ dependencies: []
636
+ ),
637
+ ]
638
+ )
639
+ """ )
640
+ let fooTargetDir = fooPkgDir. appending ( components: " Sources " , " Foo " )
641
+ try localFileSystem. createDirectory ( fooTargetDir, recursive: true )
642
+ try localFileSystem. writeFileContents ( fooTargetDir. appending ( component: " file.swift " ) , string: """
643
+ public func foo() { }
644
+ """ )
645
+
646
+ // Load a workspace from the package.
647
+ let observability = ObservabilitySystem . makeForTesting ( )
648
+ let workspace = try Workspace (
649
+ fileSystem: localFileSystem,
650
+ forRootPackage: packageDir,
651
+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
652
+ delegate: MockWorkspaceDelegate ( )
653
+ )
654
+
655
+ // Load the root manifest.
656
+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
657
+ let rootManifests = try tsc_await {
658
+ workspace. loadRootManifests (
659
+ packages: rootInput. packages,
660
+ observabilityScope: observability. topScope,
661
+ completion: $0
662
+ )
663
+ }
664
+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
665
+
666
+ // Load the package graph.
667
+ XCTAssertThrowsError ( try workspace. loadPackageGraph ( rootInput: rootInput, observabilityScope: observability. topScope) ) { error in
668
+ var diagnosed = false
669
+ if let realError = error as? PackageGraphError ,
670
+ realError. description == " target 'MyPlugin' of type 'plugin' cannot depend on 'FooLib' of type 'library' from package 'foopackage'; this dependency is unsupported " {
671
+ diagnosed = true
672
+ }
673
+ XCTAssertTrue ( diagnosed)
674
+ }
675
+ }
676
+ }
677
+
678
+ func testUnsupportedDependencyTarget( ) throws {
679
+ try testWithTemporaryDirectory { tmpPath in
680
+ // Create a sample package with a library target and a plugin.
681
+ let packageDir = tmpPath. appending ( components: " MyPackage " )
682
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
683
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
684
+ // swift-tools-version: 5.7
685
+ import PackageDescription
686
+ let package = Package(
687
+ name: " MyPackage " ,
688
+ dependencies: [
689
+ .package(path: " ../FooPackage " ),
690
+ ],
691
+ targets: [
692
+ .target(
693
+ name: " MyLibrary " ,
694
+ dependencies: []
695
+ ),
696
+ .plugin(
697
+ name: " MyPlugin " ,
698
+ capability: .buildTool(),
699
+ dependencies: [
700
+ " MyLibrary " ,
701
+ .product(name: " FooLib " , package: " FooPackage " ),
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 == " target 'MyPlugin' of type 'plugin' cannot depend on 'MyLibrary' of type 'library'; this dependency is unsupported " {
751
+ diagnosed = true
752
+ }
753
+ XCTAssertTrue ( diagnosed)
754
+ }
755
+ }
756
+ }
757
+
758
+ func testPrebuildPluginShouldNotUseExecTarget( ) throws {
759
+ try testWithTemporaryDirectory { tmpPath in
760
+ // Create a sample package with a library target and a plugin.
761
+ let packageDir = tmpPath. appending ( components: " mypkg " )
762
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
763
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
764
+ // swift-tools-version:5.6
765
+
766
+ import PackageDescription
767
+
768
+ let package = Package(
769
+ name: " mypkg " ,
770
+ products: [
771
+ .library(
772
+ name: " MyLib " ,
773
+ targets: [ " MyLib " ])
774
+ ],
775
+ targets: [
776
+ .target(
777
+ name: " MyLib " ,
778
+ plugins: [
779
+ .plugin(name: " X " )
780
+ ]),
781
+ .plugin(
782
+ name: " X " ,
783
+ capability: .buildTool(),
784
+ dependencies: [ " Y " ]
785
+ ),
786
+ .executableTarget(
787
+ name: " Y " ,
788
+ dependencies: []),
789
+ ]
790
+ )
791
+ """ )
792
+
793
+ let libTargetDir = packageDir. appending ( components: " Sources " , " MyLib " )
794
+ try localFileSystem. createDirectory ( libTargetDir, recursive: true )
795
+ try localFileSystem. writeFileContents ( libTargetDir. appending ( component: " file.swift " ) , string: """
796
+ public struct MyUtilLib {
797
+ public let strings: [String]
798
+ public init(args: [String]) {
799
+ self.strings = args
800
+ }
801
+ }
802
+ """ )
803
+
804
+ let depTargetDir = packageDir. appending ( components: " Sources " , " Y " )
805
+ try localFileSystem. createDirectory ( depTargetDir, recursive: true )
806
+ try localFileSystem. writeFileContents ( depTargetDir. appending ( component: " main.swift " ) , string: """
807
+ struct Y {
808
+ func run() {
809
+ print( " You passed us two arguments, argumentOne, and argumentTwo " )
810
+ }
811
+ }
812
+ Y.main()
813
+ """ )
814
+
815
+ let pluginTargetDir = packageDir. appending ( components: " Plugins " , " X " )
816
+ try localFileSystem. createDirectory ( pluginTargetDir, recursive: true )
817
+ try localFileSystem. writeFileContents ( pluginTargetDir. appending ( component: " plugin.swift " ) , string: """
818
+ import PackagePlugin
819
+ @main struct X: BuildToolPlugin {
820
+ func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
821
+ [
822
+ Command.prebuildCommand(
823
+ displayName: " X: Running SomeCommand before the build... " ,
824
+ executable: try context.tool(named: " Y " ).path,
825
+ arguments: [ " ARGUMENT_ONE " , " ARGUMENT_TWO " ],
826
+ outputFilesDirectory: context.pluginWorkDirectory.appending( " OUTPUT_FILES_DIRECTORY " )
827
+ )
828
+ ]
829
+ }
830
+
831
+ }
832
+ """ )
833
+
834
+ // Load a workspace from the package.
835
+ let observability = ObservabilitySystem . makeForTesting ( )
836
+ let workspace = try Workspace (
837
+ fileSystem: localFileSystem,
838
+ forRootPackage: packageDir,
839
+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
840
+ delegate: MockWorkspaceDelegate ( )
841
+ )
842
+
843
+ // Load the root manifest.
844
+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
845
+ let rootManifests = try tsc_await {
846
+ workspace. loadRootManifests (
847
+ packages: rootInput. packages,
848
+ observabilityScope: observability. topScope,
849
+ completion: $0
850
+ )
851
+ }
852
+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
853
+
854
+ // Load the package graph.
855
+ let packageGraph = try workspace. loadPackageGraph ( rootInput: rootInput, observabilityScope: observability. topScope)
856
+ XCTAssertNoDiagnostics ( observability. diagnostics)
857
+ XCTAssert ( packageGraph. packages. count == 1 , " \( packageGraph. packages) " )
858
+
859
+ // Find the build tool plugin.
860
+ let buildToolPlugin = try XCTUnwrap ( packageGraph. packages [ 0 ] . targets. map ( \. underlyingTarget) . first { $0. name == " X " } as? PluginTarget )
861
+ XCTAssertEqual ( buildToolPlugin. name, " X " )
862
+ XCTAssertEqual ( buildToolPlugin. capability, . buildTool)
863
+
864
+ // Create a plugin script runner for the duration of the test.
865
+ let pluginCacheDir = tmpPath. appending ( component: " plugin-cache " )
866
+ let pluginScriptRunner = DefaultPluginScriptRunner (
867
+ fileSystem: localFileSystem,
868
+ cacheDir: pluginCacheDir,
869
+ toolchain: try UserToolchain . default
870
+ )
871
+
872
+ // Define a plugin compilation delegate that just captures the passed information.
873
+ class Delegate : PluginScriptCompilerDelegate {
874
+ var commandLine : [ String ] ?
875
+ var environment : EnvironmentVariables ?
876
+ var compiledResult : PluginCompilationResult ?
877
+ var cachedResult : PluginCompilationResult ?
878
+ init ( ) {
879
+ }
880
+ func willCompilePlugin( commandLine: [ String ] , environment: EnvironmentVariables ) {
881
+ self . commandLine = commandLine
882
+ self . environment = environment
883
+ }
884
+ func didCompilePlugin( result: PluginCompilationResult ) {
885
+ self . compiledResult = result
886
+ }
887
+ func skippedCompilingPlugin( cachedResult: PluginCompilationResult ) {
888
+ self . cachedResult = cachedResult
889
+ }
890
+ }
891
+
892
+ // Try to compile the plugin script.
893
+ do {
894
+ // Invoke build tool plugin
895
+ let outputDir = packageDir. appending ( component: " .build " )
896
+ let builtToolsDir = outputDir. appending ( component: " debug " )
897
+ let _ = try packageGraph. invokeBuildToolPlugins (
898
+ outputDir: outputDir,
899
+ builtToolsDir: builtToolsDir,
900
+ buildEnvironment: BuildEnvironment ( platform: . macOS, configuration: . debug) ,
901
+ toolSearchDirectories: [ UserToolchain . default. swiftCompilerPath. parentDirectory] ,
902
+ pluginScriptRunner: pluginScriptRunner,
903
+ observabilityScope: observability. topScope,
904
+ fileSystem: localFileSystem
905
+ )
906
+
907
+ testDiagnostics ( observability. diagnostics) { result in
908
+ let msg = " exectuable target 'Y' is not pre-built; a plugin running a prebuild command should only rely on a pre-built binary; as a workaround, build 'Y' first and then run the plugin "
909
+ result. check ( diagnostic: . contains( msg) , severity: . error)
910
+ }
911
+ }
912
+
913
+ }
914
+ }
582
915
}
0 commit comments