diff --git a/Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Package.swift b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Package.swift similarity index 66% rename from Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Package.swift rename to Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Package.swift index 716c2fce219..fe69a01f0b5 100644 --- a/Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Package.swift +++ b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Package.swift @@ -12,6 +12,13 @@ let package = Package( description: "Writes diagnostic messages for testing" )) ), + .plugin( + name: "targetbuild-stub", + capability: .command(intent: .custom( + verb: "build-target", + description: "Build a target for testing" + )) + ), .executableTarget( name: "placeholder" ), diff --git a/Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Plugins/diagnostics_stub.swift b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/diagnostics-stub/diagnostics_stub.swift similarity index 100% rename from Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Plugins/diagnostics_stub.swift rename to Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/diagnostics-stub/diagnostics_stub.swift diff --git a/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/targetbuild-stub/targetbuild_stub.swift b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/targetbuild-stub/targetbuild_stub.swift new file mode 100644 index 00000000000..84d07337066 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Plugins/targetbuild-stub/targetbuild_stub.swift @@ -0,0 +1,25 @@ +import Foundation +import PackagePlugin + +@main +struct targetbuild_stub: CommandPlugin { + // This is a helper for testing target builds performed on behalf of plugins. + // It sends asks SwiftPM to build a target with different options depending on its arguments. + func performCommand(context: PluginContext, arguments: [String]) async throws { + // Build a target + var parameters = PackageManager.BuildParameters() + if arguments.contains("build-debug") { + parameters.configuration = .debug + } else if arguments.contains("build-release") { + parameters.configuration = .release + } else if arguments.contains("build-inherit") { + parameters.configuration = .inherit + } + // If no 'build-*' argument is present, the default (.debug) will be used. + + let _ = try packageManager.build( + .product("placeholder"), + parameters: parameters + ) + } +} \ No newline at end of file diff --git a/Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Sources/main.swift b/Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Sources/main.swift similarity index 100% rename from Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Sources/main.swift rename to Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Sources/main.swift diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index fb87deef308..e98cff936ca 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -77,6 +77,10 @@ final class PluginDelegate: PluginInvocationDelegate { buildParameters.configuration = .debug case .release: buildParameters.configuration = .release + case .inherit: + // The top level argument parser set buildParameters.configuration according to the + // --configuration command line parameter. We don't need to do anything to inherit it. + break } buildParameters.flags.cCompilerFlags.append(contentsOf: parameters.otherCFlags) buildParameters.flags.cxxCompilerFlags.append(contentsOf: parameters.otherCxxFlags) diff --git a/Sources/PackagePlugin/PackageManagerProxy.swift b/Sources/PackagePlugin/PackageManagerProxy.swift index 61116602482..5d7ee53ee99 100644 --- a/Sources/PackagePlugin/PackageManagerProxy.swift +++ b/Sources/PackagePlugin/PackageManagerProxy.swift @@ -81,7 +81,7 @@ public struct PackageManager { /// Represents an overall purpose of the build, which affects such things /// as optimization and generation of debug symbols. public enum BuildConfiguration: String { - case debug, release + case debug, release, inherit } /// Represents the amount of detail in a build log. @@ -328,6 +328,8 @@ fileprivate extension PluginToHostMessage.BuildParameters.Configuration { self = .debug case .release: self = .release + case .inherit: + self = .inherit } } } diff --git a/Sources/PackagePlugin/PluginMessages.swift b/Sources/PackagePlugin/PluginMessages.swift index 5e4bf372aed..be17ed0bd19 100644 --- a/Sources/PackagePlugin/PluginMessages.swift +++ b/Sources/PackagePlugin/PluginMessages.swift @@ -297,7 +297,7 @@ enum PluginToHostMessage: Codable { struct BuildParameters: Codable { var configuration: Configuration enum Configuration: String, Codable { - case debug, release + case debug, release, inherit } var logging: LogVerbosity enum LogVerbosity: String, Codable { diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index c6a7391c174..f6c92fc834d 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -861,7 +861,7 @@ public enum PluginInvocationBuildSubset { public struct PluginInvocationBuildParameters { public var configuration: Configuration public enum Configuration: String { - case debug, release + case debug, release, inherit } public var logging: LogVerbosity public enum LogVerbosity: String { @@ -993,6 +993,8 @@ fileprivate extension PluginInvocationBuildParameters.Configuration { self = .debug case .release: self = .release + case .inherit: + self = .inherit } } } diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index 6ea628d70f7..4e770167975 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -1890,7 +1890,7 @@ final class PackageToolTests: CommandsTestCase { let containsWarning = StringPattern.contains("command plugin: Diagnostics.warning") let containsError = StringPattern.contains("command plugin: Diagnostics.error") - try fixture(name: "Miscellaneous/Plugins/CommandPluginDiagnosticsStub") { fixturePath in + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in func runPlugin(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws { let (stdout, stderr) = try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath) completion(stdout, stderr) @@ -1987,6 +1987,68 @@ final class PackageToolTests: CommandsTestCase { } } + // Test target builds requested by a command plugin + func testCommandPluginTargetBuilds() throws { + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). + try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") + + let debugTarget = [".build", "debug", "placeholder"] + let releaseTarget = [".build", "release", "placeholder"] + + func AssertIsExecutableFile(_ fixturePath: AbsolutePath, file: StaticString = #filePath, line: UInt = #line) { + XCTAssert( + localFileSystem.isExecutableFile(fixturePath), + "\(fixturePath) does not exist", + file: file, + line: line + ) + } + + func AssertNotExists(_ fixturePath: AbsolutePath, file: StaticString = #filePath, line: UInt = #line) { + XCTAssertFalse( + localFileSystem.exists(fixturePath), + "\(fixturePath) should not exist", + file: file, + line: line + ) + } + + // By default, a plugin-requested build produces a debug binary + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in + let _ = try SwiftPM.Package.execute(["-c", "release", "build-target"], packagePath: fixturePath) + AssertIsExecutableFile(fixturePath.appending(components: debugTarget)) + AssertNotExists(fixturePath.appending(components: releaseTarget)) + } + + // If the plugin specifies a debug binary, that is what will be built, regardless of overall configuration + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in + let _ = try SwiftPM.Package.execute(["-c", "release", "build-target", "build-debug"], packagePath: fixturePath) + AssertIsExecutableFile(fixturePath.appending(components: debugTarget)) + AssertNotExists(fixturePath.appending(components: releaseTarget)) + } + + // If the plugin requests a release binary, that is what will be built, regardless of overall configuration + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in + let _ = try SwiftPM.Package.execute(["-c", "debug", "build-target", "build-release"], packagePath: fixturePath) + AssertNotExists(fixturePath.appending(components: debugTarget)) + AssertIsExecutableFile(fixturePath.appending(components: releaseTarget)) + } + + // If the plugin inherits the overall build configuration, that is what will be built + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in + let _ = try SwiftPM.Package.execute(["-c", "debug", "build-target", "build-inherit"], packagePath: fixturePath) + AssertIsExecutableFile(fixturePath.appending(components: debugTarget)) + AssertNotExists(fixturePath.appending(components: releaseTarget)) + } + + // If the plugin inherits the overall build configuration, that is what will be built + try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in + let _ = try SwiftPM.Package.execute(["-c", "release", "build-target", "build-inherit"], packagePath: fixturePath) + AssertNotExists(fixturePath.appending(components: debugTarget)) + AssertIsExecutableFile(fixturePath.appending(components: releaseTarget)) + } + } + func testCommandPluginNetworkingPermissions(permissionsManifestFragment: String, permissionError: String, reason: String, remedy: [String]) throws { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")