Skip to content

Commit 3ef830d

Browse files
authored
command plugins: Inherit SwiftPM's --configuration flag in packageManager.build (#7262)
This commit makes it possible for a build run on behalf of a command plugin to inherit the build configuration (debug or release) set for the whole `swift package` run using the `--configuration` flag. ### Motivation: When a command plugin asks for a target to be built by calling `packageManager.build`, it must specify a release or debug build. If no configuration is given, debug is the default. This overrides any configuration specified by the user with `swift package -c <debug|release>`. A command plugin might often be used as an alterative entry point to Swift PM, responsible for building a target and then processing it in some way to generate the final product. The user might run `swift package -c release some-plugin --target some-target`, expecting a release build of target to be made, but currently the result will be a debug binary if the plugin uses the default options. ### Modifications: * Added a new `.inherit` option for packageManager.build's `configuration` argument.; `.debug` remains the default, as before. * Added a test to verify that plugin-initiated builds are of the correct type. ### Result: A command plugin can request a target build matching the configuration specified on the SwiftPM command line. The default is still to make a debug build, however in future we might change this to inherit the overall SwiftPM configuration. ### Alternatives: A command plugin does not currently seem to have access to SwiftPM's build configuration. We could pass this information to the plugin, allowing the plugin author to pass it back in the `packageManager.build` call. This would be a less invasive change to SwiftPM, however the approach in this commit makes it easier to change the default to .inherit in the future.
1 parent c824e63 commit 3ef830d

File tree

9 files changed

+106
-4
lines changed

9 files changed

+106
-4
lines changed

Fixtures/Miscellaneous/Plugins/CommandPluginDiagnosticsStub/Package.swift renamed to Fixtures/Miscellaneous/Plugins/CommandPluginTestStub/Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ let package = Package(
1212
description: "Writes diagnostic messages for testing"
1313
))
1414
),
15+
.plugin(
16+
name: "targetbuild-stub",
17+
capability: .command(intent: .custom(
18+
verb: "build-target",
19+
description: "Build a target for testing"
20+
))
21+
),
1522
.executableTarget(
1623
name: "placeholder"
1724
),
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
import PackagePlugin
3+
4+
@main
5+
struct targetbuild_stub: CommandPlugin {
6+
// This is a helper for testing target builds performed on behalf of plugins.
7+
// It sends asks SwiftPM to build a target with different options depending on its arguments.
8+
func performCommand(context: PluginContext, arguments: [String]) async throws {
9+
// Build a target
10+
var parameters = PackageManager.BuildParameters()
11+
if arguments.contains("build-debug") {
12+
parameters.configuration = .debug
13+
} else if arguments.contains("build-release") {
14+
parameters.configuration = .release
15+
} else if arguments.contains("build-inherit") {
16+
parameters.configuration = .inherit
17+
}
18+
// If no 'build-*' argument is present, the default (.debug) will be used.
19+
20+
let _ = try packageManager.build(
21+
.product("placeholder"),
22+
parameters: parameters
23+
)
24+
}
25+
}

Sources/Commands/Utilities/PluginDelegate.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ final class PluginDelegate: PluginInvocationDelegate {
7777
buildParameters.configuration = .debug
7878
case .release:
7979
buildParameters.configuration = .release
80+
case .inherit:
81+
// The top level argument parser set buildParameters.configuration according to the
82+
// --configuration command line parameter. We don't need to do anything to inherit it.
83+
break
8084
}
8185
buildParameters.flags.cCompilerFlags.append(contentsOf: parameters.otherCFlags)
8286
buildParameters.flags.cxxCompilerFlags.append(contentsOf: parameters.otherCxxFlags)

Sources/PackagePlugin/PackageManagerProxy.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public struct PackageManager {
8181
/// Represents an overall purpose of the build, which affects such things
8282
/// as optimization and generation of debug symbols.
8383
public enum BuildConfiguration: String {
84-
case debug, release
84+
case debug, release, inherit
8585
}
8686

8787
/// Represents the amount of detail in a build log.
@@ -328,6 +328,8 @@ fileprivate extension PluginToHostMessage.BuildParameters.Configuration {
328328
self = .debug
329329
case .release:
330330
self = .release
331+
case .inherit:
332+
self = .inherit
331333
}
332334
}
333335
}

Sources/PackagePlugin/PluginMessages.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ enum PluginToHostMessage: Codable {
297297
struct BuildParameters: Codable {
298298
var configuration: Configuration
299299
enum Configuration: String, Codable {
300-
case debug, release
300+
case debug, release, inherit
301301
}
302302
var logging: LogVerbosity
303303
enum LogVerbosity: String, Codable {

Sources/SPMBuildCore/Plugins/PluginInvocation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ public enum PluginInvocationBuildSubset {
861861
public struct PluginInvocationBuildParameters {
862862
public var configuration: Configuration
863863
public enum Configuration: String {
864-
case debug, release
864+
case debug, release, inherit
865865
}
866866
public var logging: LogVerbosity
867867
public enum LogVerbosity: String {
@@ -993,6 +993,8 @@ fileprivate extension PluginInvocationBuildParameters.Configuration {
993993
self = .debug
994994
case .release:
995995
self = .release
996+
case .inherit:
997+
self = .inherit
996998
}
997999
}
9981000
}

Tests/CommandsTests/PackageToolTests.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,7 @@ final class PackageToolTests: CommandsTestCase {
18901890
let containsWarning = StringPattern.contains("command plugin: Diagnostics.warning")
18911891
let containsError = StringPattern.contains("command plugin: Diagnostics.error")
18921892

1893-
try fixture(name: "Miscellaneous/Plugins/CommandPluginDiagnosticsStub") { fixturePath in
1893+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
18941894
func runPlugin(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws {
18951895
let (stdout, stderr) = try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath)
18961896
completion(stdout, stderr)
@@ -1987,6 +1987,68 @@ final class PackageToolTests: CommandsTestCase {
19871987
}
19881988
}
19891989

1990+
// Test target builds requested by a command plugin
1991+
func testCommandPluginTargetBuilds() throws {
1992+
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
1993+
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")
1994+
1995+
let debugTarget = [".build", "debug", "placeholder"]
1996+
let releaseTarget = [".build", "release", "placeholder"]
1997+
1998+
func AssertIsExecutableFile(_ fixturePath: AbsolutePath, file: StaticString = #filePath, line: UInt = #line) {
1999+
XCTAssert(
2000+
localFileSystem.isExecutableFile(fixturePath),
2001+
"\(fixturePath) does not exist",
2002+
file: file,
2003+
line: line
2004+
)
2005+
}
2006+
2007+
func AssertNotExists(_ fixturePath: AbsolutePath, file: StaticString = #filePath, line: UInt = #line) {
2008+
XCTAssertFalse(
2009+
localFileSystem.exists(fixturePath),
2010+
"\(fixturePath) should not exist",
2011+
file: file,
2012+
line: line
2013+
)
2014+
}
2015+
2016+
// By default, a plugin-requested build produces a debug binary
2017+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
2018+
let _ = try SwiftPM.Package.execute(["-c", "release", "build-target"], packagePath: fixturePath)
2019+
AssertIsExecutableFile(fixturePath.appending(components: debugTarget))
2020+
AssertNotExists(fixturePath.appending(components: releaseTarget))
2021+
}
2022+
2023+
// If the plugin specifies a debug binary, that is what will be built, regardless of overall configuration
2024+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
2025+
let _ = try SwiftPM.Package.execute(["-c", "release", "build-target", "build-debug"], packagePath: fixturePath)
2026+
AssertIsExecutableFile(fixturePath.appending(components: debugTarget))
2027+
AssertNotExists(fixturePath.appending(components: releaseTarget))
2028+
}
2029+
2030+
// If the plugin requests a release binary, that is what will be built, regardless of overall configuration
2031+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
2032+
let _ = try SwiftPM.Package.execute(["-c", "debug", "build-target", "build-release"], packagePath: fixturePath)
2033+
AssertNotExists(fixturePath.appending(components: debugTarget))
2034+
AssertIsExecutableFile(fixturePath.appending(components: releaseTarget))
2035+
}
2036+
2037+
// If the plugin inherits the overall build configuration, that is what will be built
2038+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
2039+
let _ = try SwiftPM.Package.execute(["-c", "debug", "build-target", "build-inherit"], packagePath: fixturePath)
2040+
AssertIsExecutableFile(fixturePath.appending(components: debugTarget))
2041+
AssertNotExists(fixturePath.appending(components: releaseTarget))
2042+
}
2043+
2044+
// If the plugin inherits the overall build configuration, that is what will be built
2045+
try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in
2046+
let _ = try SwiftPM.Package.execute(["-c", "release", "build-target", "build-inherit"], packagePath: fixturePath)
2047+
AssertNotExists(fixturePath.appending(components: debugTarget))
2048+
AssertIsExecutableFile(fixturePath.appending(components: releaseTarget))
2049+
}
2050+
}
2051+
19902052
func testCommandPluginNetworkingPermissions(permissionsManifestFragment: String, permissionError: String, reason: String, remedy: [String]) throws {
19912053
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
19922054
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")

0 commit comments

Comments
 (0)