@@ -808,6 +808,177 @@ final class PluginInvocationTests: XCTestCase {
808808 }
809809 }
810810
811+ func testPrebuildPluginShouldUseBinaryTarget( ) async throws {
812+ // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
813+ try XCTSkipIf ( !UserToolchain. default. supportsSwiftConcurrency ( ) , " skipping because test environment doesn't support concurrency " )
814+
815+ try await testWithTemporaryDirectory { tmpPath in
816+ // Create a sample package with a library target and a plugin.
817+ let packageDir = tmpPath. appending ( components: " mypkg " )
818+ try localFileSystem. createDirectory ( packageDir, recursive: true )
819+ try localFileSystem. writeFileContents ( packageDir. appending ( " Package.swift " ) , string: """
820+ // swift-tools-version:5.7
821+
822+ import PackageDescription
823+
824+ let package = Package(
825+ name: " mypkg " ,
826+ products: [
827+ .library(
828+ name: " MyLib " ,
829+ targets: [ " MyLib " ])
830+ ],
831+ targets: [
832+ .target(
833+ name: " MyLib " ,
834+ plugins: [
835+ .plugin(name: " X " )
836+ ]),
837+ .plugin(
838+ name: " X " ,
839+ capability: .buildTool(),
840+ dependencies: [ " Y " ]
841+ ),
842+ .binaryTarget(
843+ name: " Y " ,
844+ path: " Binaries/Y. \( artifactBundleExtension) "
845+ ),
846+ ]
847+ )
848+ """ )
849+
850+ let libTargetDir = packageDir. appending ( components: " Sources " , " MyLib " )
851+ try localFileSystem. createDirectory ( libTargetDir, recursive: true )
852+ try localFileSystem. writeFileContents ( libTargetDir. appending ( " file.swift " ) , string: """
853+ public struct MyUtilLib {
854+ public let strings: [String]
855+ public init(args: [String]) {
856+ self.strings = args
857+ }
858+ }
859+ """ )
860+
861+ let depTargetDir = packageDir. appending ( components: " Sources " , " Y " )
862+ try localFileSystem. createDirectory ( depTargetDir, recursive: true )
863+ try localFileSystem. writeFileContents ( depTargetDir. appending ( " main.swift " ) , string: """
864+ struct Y {
865+ func run() {
866+ print( " You passed us two arguments, argumentOne, and argumentTwo " )
867+ }
868+ }
869+ Y.main()
870+ """ )
871+
872+ let pluginTargetDir = packageDir. appending ( components: " Plugins " , " X " )
873+ try localFileSystem. createDirectory ( pluginTargetDir, recursive: true )
874+ try localFileSystem. writeFileContents ( pluginTargetDir. appending ( " plugin.swift " ) , string: """
875+ import PackagePlugin
876+ @main struct X: BuildToolPlugin {
877+ func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
878+ [
879+ Command.prebuildCommand(
880+ displayName: " X: Running Y before the build... " ,
881+ executable: try context.tool(named: " Y " ).path,
882+ arguments: [ " ARGUMENT_ONE " , " ARGUMENT_TWO " ],
883+ outputFilesDirectory: context.pluginWorkDirectory.appending( " OUTPUT_FILES_DIRECTORY " )
884+ )
885+ ]
886+ }
887+ }
888+ """ )
889+
890+ let artifactVariants = [ try UserToolchain . default. targetTriple] . map {
891+ """
892+ { " path " : " Y " , " supportedTriples " : [ " \( $0. tripleString) " ] }
893+ """
894+ }
895+
896+ let bundlePath = packageDir. appending ( components: " Binaries " , " Y. \( artifactBundleExtension) " )
897+ let bundleMetadataPath = bundlePath. appending ( component: " info.json " )
898+ try localFileSystem. createDirectory ( bundleMetadataPath. parentDirectory, recursive: true )
899+ try localFileSystem. writeFileContents (
900+ bundleMetadataPath,
901+ string: """
902+ { " schemaVersion " : " 1.0 " ,
903+ " artifacts " : {
904+ " Y " : {
905+ " type " : " executable " ,
906+ " version " : " 1.2.3 " ,
907+ " variants " : [
908+ \( artifactVariants. joined ( separator: " , " ) )
909+ ]
910+ }
911+ }
912+ }
913+ """
914+ )
915+ let binaryPath = bundlePath. appending ( component: " Y " )
916+ try localFileSystem. writeFileContents ( binaryPath, string: " " )
917+
918+ // Load a workspace from the package.
919+ let observability = ObservabilitySystem . makeForTesting ( )
920+ let workspace = try Workspace (
921+ fileSystem: localFileSystem,
922+ forRootPackage: packageDir,
923+ customManifestLoader: ManifestLoader ( toolchain: UserToolchain . default) ,
924+ delegate: MockWorkspaceDelegate ( )
925+ )
926+
927+ // Load the root manifest.
928+ let rootInput = PackageGraphRootInput ( packages: [ packageDir] , dependencies: [ ] )
929+ let rootManifests = try await workspace. loadRootManifests (
930+ packages: rootInput. packages,
931+ observabilityScope: observability. topScope
932+ )
933+ XCTAssert ( rootManifests. count == 1 , " \( rootManifests) " )
934+
935+ // Load the package graph.
936+ let packageGraph = try await workspace. loadPackageGraph (
937+ rootInput: rootInput,
938+ observabilityScope: observability. topScope
939+ )
940+ XCTAssertNoDiagnostics ( observability. diagnostics)
941+ XCTAssert ( packageGraph. packages. count == 1 , " \( packageGraph. packages) " )
942+
943+ // Find the build tool plugin.
944+ let buildToolPlugin = try XCTUnwrap ( packageGraph. packages. first? . modules. map ( \. underlying) . filter { $0. name == " X " } . first as? PluginModule )
945+ XCTAssertEqual ( buildToolPlugin. name, " X " )
946+ XCTAssertEqual ( buildToolPlugin. capability, . buildTool)
947+
948+ // Create a plugin script runner for the duration of the test.
949+ let pluginCacheDir = tmpPath. appending ( " plugin-cache " )
950+ let pluginScriptRunner = DefaultPluginScriptRunner (
951+ fileSystem: localFileSystem,
952+ cacheDir: pluginCacheDir,
953+ toolchain: try UserToolchain . default
954+ )
955+
956+ // Invoke build tool plugin
957+ do {
958+ let outputDir = packageDir. appending ( " .build " )
959+ let buildParameters = mockBuildParameters (
960+ destination: . host,
961+ environment: BuildEnvironment ( platform: . macOS, configuration: . debug)
962+ )
963+
964+ let result = try await invokeBuildToolPlugins (
965+ graph: packageGraph,
966+ buildParameters: buildParameters,
967+ fileSystem: localFileSystem,
968+ outputDir: outputDir,
969+ pluginScriptRunner: pluginScriptRunner,
970+ observabilityScope: observability. topScope
971+ )
972+
973+ let diags = result. flatMap ( \. value. results) . flatMap ( \. diagnostics)
974+ testDiagnostics ( diags) { result in
975+ result. checkIsEmpty ( )
976+ }
977+ }
978+ }
979+ }
980+
981+
811982 func testPrebuildPluginShouldNotUseExecTarget( ) async throws {
812983 // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
813984 try XCTSkipIf ( !UserToolchain. default. supportsSwiftConcurrency ( ) , " skipping because test environment doesn't support concurrency " )
@@ -1201,7 +1372,7 @@ final class PluginInvocationTests: XCTestCase {
12011372 try localFileSystem. writeFileContents ( myPluginTargetDir. appending ( " plugin.swift " ) , string: content)
12021373 let artifactVariants = artifactSupportedTriples. map {
12031374 """
1204- { " path " : " LocalBinaryTool \( $0. tripleString) .sh " , " supportedTriples " : [ " \( $0. tripleString) " ] }
1375+ { " path " : " \( $0. tripleString) /LocalBinaryTool " , " supportedTriples " : [ " \( $0. tripleString) " ] }
12051376 """
12061377 }
12071378
@@ -1337,7 +1508,7 @@ final class PluginInvocationTests: XCTestCase {
13371508 $0. value. forEach {
13381509 XCTAssertTrue ( $0. succeeded, " plugin unexpectedly failed " )
13391510 XCTAssertEqual ( $0. diagnostics. map { $0. message } , [ ] , " plugin produced unexpected diagnostics " )
1340- XCTAssertEqual ( $0. buildCommands. first? . configuration. executable. basename, " LocalBinaryTool \( hostTriple . tripleString ) .sh " )
1511+ XCTAssertEqual ( $0. buildCommands. first? . configuration. executable. basename, " LocalBinaryTool " )
13411512 }
13421513 }
13431514 }
0 commit comments