Skip to content

Commit 7b6e99f

Browse files
committed
command plugins: Inherit SwiftPM's --configuration flag in packageManager.build
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 9920e3b commit 7b6e99f

File tree

7 files changed

+105
-3
lines changed

7 files changed

+105
-3
lines changed

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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,6 +1983,68 @@ final class PackageToolTests: CommandsTestCase {
19831983
}
19841984
}
19851985

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

0 commit comments

Comments
 (0)