@@ -579,4 +579,162 @@ class PluginInvocationTests: XCTestCase {
579
579
}
580
580
}
581
581
}
582
+
583
+ func testPrebuildPluginShouldNotUseExecTarget( ) throws {
584
+ try testWithTemporaryDirectory { tmpPath in
585
+ // Create a sample package with a library target and a plugin.
586
+ let packageDir = tmpPath. appending ( components: " mypkg " )
587
+ try localFileSystem. createDirectory ( packageDir, recursive: true )
588
+ try localFileSystem. writeFileContents ( packageDir. appending ( component: " Package.swift " ) , string: """
589
+ // swift-tools-version:5.6
590
+
591
+ import PackageDescription
592
+
593
+ let package = Package(
594
+ name: " mypkg " ,
595
+ products: [
596
+ .library(
597
+ name: " MyLib " ,
598
+ targets: [ " MyLib " ])
599
+ ],
600
+ targets: [
601
+ .target(
602
+ name: " MyLib " ,
603
+ plugins: [
604
+ .plugin(name: " X " )
605
+ ]),
606
+ .plugin(
607
+ name: " X " ,
608
+ capability: .buildTool(),
609
+ dependencies: [ " Y " ]
610
+ ),
611
+ .executableTarget(
612
+ name: " Y " ,
613
+ dependencies: []),
614
+ ]
615
+ )
616
+ """ )
617
+
618
+ let libTargetDir = packageDir. appending ( components: " Sources " , " MyLib " )
619
+ try localFileSystem. createDirectory ( libTargetDir, recursive: true )
620
+ try localFileSystem. writeFileContents ( libTargetDir. appending ( component: " file.swift " ) , string: """
621
+ public struct MyUtilLib {
622
+ public let strings: [String]
623
+ public init(args: [String]) {
624
+ self.strings = args
625
+ }
626
+ }
627
+ """ )
628
+
629
+ let depTargetDir = packageDir. appending ( components: " Sources " , " Y " )
630
+ try localFileSystem. createDirectory ( depTargetDir, recursive: true )
631
+ try localFileSystem. writeFileContents ( depTargetDir. appending ( component: " main.swift " ) , string: """
632
+ struct Y {
633
+ func run() {
634
+ print( " You passed us two arguments, argumentOne, and argumentTwo " )
635
+ }
636
+ }
637
+ Y.main()
638
+ """ )
639
+
640
+ let pluginTargetDir = packageDir. appending ( components: " Plugins " , " X " )
641
+ try localFileSystem. createDirectory ( pluginTargetDir, recursive: true )
642
+ try localFileSystem. writeFileContents ( pluginTargetDir. appending ( component: " plugin.swift " ) , string: """
643
+ import PackagePlugin
644
+ @main struct X: BuildToolPlugin {
645
+ func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
646
+ [
647
+ Command.prebuildCommand(
648
+ displayName: " X: Running SomeCommand before the build... " ,
649
+ executable: try context.tool(named: " Y " ).path,
650
+ arguments: [ " ARGUMENT_ONE " , " ARGUMENT_TWO " ],
651
+ outputFilesDirectory: context.pluginWorkDirectory.appending( " OUTPUT_FILES_DIRECTORY " )
652
+ )
653
+ ]
654
+ }
655
+
656
+ }
657
+ """ )
658
+
659
+ // Load a workspace from the package.
660
+ let observability = ObservabilitySystem . makeForTesting ( )
661
+ let workspace = try Workspace (
662
+ fileSystem: localFileSystem,
663
+ forRootPackage: packageDir,
664
+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
665
+ delegate: MockWorkspaceDelegate ( )
666
+ )
667
+
668
+ // Load the root manifest.
669
+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
670
+ let rootManifests = try tsc_await {
671
+ workspace. loadRootManifests (
672
+ packages: rootInput. packages,
673
+ observabilityScope: observability. topScope,
674
+ completion: $0
675
+ )
676
+ }
677
+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
678
+
679
+ // Load the package graph.
680
+ let packageGraph = try workspace. loadPackageGraph ( rootInput: rootInput, observabilityScope: observability. topScope)
681
+ XCTAssertNoDiagnostics ( observability. diagnostics)
682
+ XCTAssert ( packageGraph. packages. count == 1 , " \( packageGraph. packages) " )
683
+
684
+ // Find the build tool plugin.
685
+ let buildToolPlugin = try XCTUnwrap ( packageGraph. packages [ 0 ] . targets. map ( \. underlyingTarget) . first { $0. name == " X " } as? PluginTarget )
686
+ XCTAssertEqual ( buildToolPlugin. name, " X " )
687
+ XCTAssertEqual ( buildToolPlugin. capability, . buildTool)
688
+
689
+ // Create a plugin script runner for the duration of the test.
690
+ let pluginCacheDir = tmpPath. appending ( component: " plugin-cache " )
691
+ let pluginScriptRunner = DefaultPluginScriptRunner (
692
+ fileSystem: localFileSystem,
693
+ cacheDir: pluginCacheDir,
694
+ toolchain: try UserToolchain . default
695
+ )
696
+
697
+ // Define a plugin compilation delegate that just captures the passed information.
698
+ class Delegate : PluginScriptCompilerDelegate {
699
+ var commandLine : [ String ] ?
700
+ var environment : EnvironmentVariables ?
701
+ var compiledResult : PluginCompilationResult ?
702
+ var cachedResult : PluginCompilationResult ?
703
+ init ( ) {
704
+ }
705
+ func willCompilePlugin( commandLine: [ String ] , environment: EnvironmentVariables ) {
706
+ self . commandLine = commandLine
707
+ self . environment = environment
708
+ }
709
+ func didCompilePlugin( result: PluginCompilationResult ) {
710
+ self . compiledResult = result
711
+ }
712
+ func skippedCompilingPlugin( cachedResult: PluginCompilationResult ) {
713
+ self . cachedResult = cachedResult
714
+ }
715
+ }
716
+
717
+ // Try to compile the plugin script.
718
+ do {
719
+ // Invoke build tool plugin
720
+ let outputDir = packageDir. appending ( component: " .build " )
721
+ let builtToolsDir = outputDir. appending ( component: " debug " )
722
+ let _ = try packageGraph. invokeBuildToolPlugins (
723
+ outputDir: outputDir,
724
+ builtToolsDir: builtToolsDir,
725
+ buildEnvironment: BuildEnvironment ( platform: . macOS, configuration: . debug) ,
726
+ toolSearchDirectories: [ UserToolchain . default. swiftCompilerPath. parentDirectory] ,
727
+ pluginScriptRunner: pluginScriptRunner,
728
+ observabilityScope: observability. topScope,
729
+ fileSystem: localFileSystem
730
+ )
731
+
732
+ testDiagnostics ( observability. diagnostics) { result in
733
+ 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 "
734
+ result. check ( diagnostic: . contains( msg) , severity: . error)
735
+ }
736
+ }
737
+
738
+ }
739
+ }
582
740
}
0 commit comments