@@ -583,4 +583,206 @@ class PluginInvocationTests: XCTestCase {
583
583
}
584
584
}
585
585
}
586
+
587
+ func testPrebuildPluginShouldNotUseExecTarget( ) throws {
588
+ try testWithTemporaryDirectory { tmpPath in
589
+ // Create a sample package with a library target and a plugin.
590
+ let packageDir = tmpPath. appending ( components: " mypkg " )
591
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
592
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
593
+ // swift-tools-version:5.7
594
+
595
+ import PackageDescription
596
+
597
+ let package = Package(
598
+ name: " mypkg " ,
599
+ products: [
600
+ .library(
601
+ name: " MyLib " ,
602
+ targets: [ " MyLib " ])
603
+ ],
604
+ targets: [
605
+ .target(
606
+ name: " MyLib " ,
607
+ plugins: [
608
+ .plugin(name: " X " )
609
+ ]),
610
+ .plugin(
611
+ name: " X " ,
612
+ capability: .buildTool(),
613
+ dependencies: [ " Y " ]
614
+ ),
615
+ .executableTarget(
616
+ name: " Y " ,
617
+ dependencies: []),
618
+ ]
619
+ )
620
+ """ )
621
+
622
+ let libTargetDir = packageDir. appending ( components: " Sources " , " MyLib " )
623
+ try localFileSystem. createDirectory ( libTargetDir, recursive: true )
624
+ try localFileSystem. writeFileContents ( libTargetDir. appending ( component: " file.swift " ) , string: """
625
+ public struct MyUtilLib {
626
+ public let strings: [String]
627
+ public init(args: [String]) {
628
+ self.strings = args
629
+ }
630
+ }
631
+ """ )
632
+
633
+ let depTargetDir = packageDir. appending ( components: " Sources " , " Y " )
634
+ try localFileSystem. createDirectory ( depTargetDir, recursive: true )
635
+ try localFileSystem. writeFileContents ( depTargetDir. appending ( component: " main.swift " ) , string: """
636
+ struct Y {
637
+ func run() {
638
+ print( " You passed us two arguments, argumentOne, and argumentTwo " )
639
+ }
640
+ }
641
+ Y.main()
642
+ """ )
643
+
644
+ let pluginTargetDir = packageDir. appending ( components: " Plugins " , " X " )
645
+ try localFileSystem. createDirectory ( pluginTargetDir, recursive: true )
646
+ try localFileSystem. writeFileContents ( pluginTargetDir. appending ( component: " plugin.swift " ) , string: """
647
+ import PackagePlugin
648
+ @main struct X: BuildToolPlugin {
649
+ func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
650
+ [
651
+ Command.prebuildCommand(
652
+ displayName: " X: Running Y before the build... " ,
653
+ executable: try context.tool(named: " Y " ).path,
654
+ arguments: [ " ARGUMENT_ONE " , " ARGUMENT_TWO " ],
655
+ outputFilesDirectory: context.pluginWorkDirectory.appending( " OUTPUT_FILES_DIRECTORY " )
656
+ )
657
+ ]
658
+ }
659
+
660
+ }
661
+ """ )
662
+
663
+ // Load a workspace from the package.
664
+ let observability = ObservabilitySystem . makeForTesting ( )
665
+ let workspace = try Workspace (
666
+ fileSystem: localFileSystem,
667
+ forRootPackage: packageDir,
668
+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
669
+ delegate: MockWorkspaceDelegate ( )
670
+ )
671
+
672
+ // Load the root manifest.
673
+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
674
+ let rootManifests = try tsc_await {
675
+ workspace. loadRootManifests (
676
+ packages: rootInput. packages,
677
+ observabilityScope: observability. topScope,
678
+ completion: $0
679
+ )
680
+ }
681
+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
682
+
683
+ // Load the package graph.
684
+ let packageGraph = try workspace. loadPackageGraph ( rootInput: rootInput, observabilityScope: observability. topScope)
685
+ XCTAssertNoDiagnostics ( observability. diagnostics)
686
+ XCTAssert ( packageGraph. packages. count == 1 , " \( packageGraph. packages) " )
687
+
688
+ // Find the build tool plugin.
689
+ let buildToolPlugin = try XCTUnwrap ( packageGraph. packages [ 0 ] . targets. map ( \. underlyingTarget) . filter { $0. name == " X " } . first as? PluginTarget )
690
+ XCTAssertEqual ( buildToolPlugin. name, " X " )
691
+ XCTAssertEqual ( buildToolPlugin. capability, . buildTool)
692
+
693
+ // Create a plugin script runner for the duration of the test.
694
+ let pluginCacheDir = tmpPath. appending ( component: " plugin-cache " )
695
+ let pluginScriptRunner = DefaultPluginScriptRunner (
696
+ fileSystem: localFileSystem,
697
+ cacheDir: pluginCacheDir,
698
+ toolchain: try UserToolchain . default
699
+ )
700
+
701
+ // Invoke build tool plugin
702
+ do {
703
+ let outputDir = packageDir. appending ( component: " .build " )
704
+ let builtToolsDir = outputDir. appending ( component: " debug " )
705
+ let result = try packageGraph. invokeBuildToolPlugins (
706
+ outputDir: outputDir,
707
+ builtToolsDir: builtToolsDir,
708
+ buildEnvironment: BuildEnvironment ( platform: . macOS, configuration: . debug) ,
709
+ toolSearchDirectories: [ UserToolchain . default. swiftCompilerPath. parentDirectory] ,
710
+ pluginScriptRunner: pluginScriptRunner,
711
+ observabilityScope: observability. topScope,
712
+ fileSystem: localFileSystem
713
+ )
714
+
715
+ let diags = result. map { $0. value} . flatMap { $0} . map { $0. diagnostics} . flatMap { $0}
716
+ testDiagnostics ( diags) { result in
717
+ let msg = " exectuable target 'Y' is not pre-built; a plugin running a prebuild command should only rely on an existing binary; as a workaround, build 'Y' first and then run the plugin "
718
+ result. check ( diagnostic: . contains( msg) , severity: . error)
719
+ }
720
+ }
721
+ }
722
+ }
723
+
724
+ func testShouldNotRequireNonPluginTarget( ) throws {
725
+ try testWithTemporaryDirectory { tmpPath in
726
+ // Create a sample package with a library target and a plugin.
727
+ let packageDir = tmpPath. appending ( components: " MyPackage " )
728
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
729
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
730
+ // swift-tools-version: 5.7
731
+ import PackageDescription
732
+ let package = Package(
733
+ name: " MyPackage " ,
734
+ products: [
735
+ .plugin(name: " Foo " , targets: [ " Foo " ])
736
+ ],
737
+ dependencies: [
738
+ ],
739
+ targets: [
740
+ .plugin(
741
+ name: " Foo " ,
742
+ capability: .command(
743
+ intent: .custom(verb: " Foo " , description: " Plugin example " ),
744
+ permissions: []
745
+ )
746
+ )
747
+ ]
748
+ )
749
+ """ )
750
+
751
+ let myPluginTargetDir = packageDir. appending ( components: " Plugins " , " Foo " )
752
+ try localFileSystem. createDirectory ( myPluginTargetDir, recursive: true )
753
+ try localFileSystem. writeFileContents ( myPluginTargetDir. appending ( component: " plugin.swift " ) , string: """
754
+ import PackagePlugin
755
+ @main struct FooPlugin: BuildToolPlugin {
756
+ func createBuildCommands(
757
+ context: PluginContext,
758
+ target: Target
759
+ ) throws -> [Command] { }
760
+ }
761
+ """ )
762
+
763
+ // Load a workspace from the package.
764
+ let observability = ObservabilitySystem . makeForTesting ( )
765
+ let workspace = try Workspace (
766
+ fileSystem: localFileSystem,
767
+ forRootPackage: packageDir,
768
+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
769
+ delegate: MockWorkspaceDelegate ( )
770
+ )
771
+
772
+ // Load the root manifest.
773
+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
774
+ let rootManifests = try tsc_await {
775
+ workspace. loadRootManifests (
776
+ packages: rootInput. packages,
777
+ observabilityScope: observability. topScope,
778
+ completion: $0
779
+ )
780
+ }
781
+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
782
+
783
+ // Load the package graph.
784
+ let _ = try workspace. loadPackageGraph ( rootInput: rootInput, observabilityScope: observability. topScope)
785
+ XCTAssertNoDiagnostics ( observability. diagnostics)
786
+ }
787
+ }
586
788
}
0 commit comments