Skip to content

Commit dbeda87

Browse files
[Traits] Add experimental flags to enable specific or all traits, disable default traits (#7694)
# Motivation When building, testing or running a root package we want users to be able to change the traits enabled in the packages. This allows them to build binaries with different traits enabled or run tests with different traits. # Modification This PR adds trait options to a few commands and wires them through to the ModuleGraph loading and the build description caching. The latter is important to trigger rebuilds when the trait configuration has changed. Along the way I refactored the `GraphLoadingNode` and externalised the logic to calculate traits since I need slightly different logic for the root nodes and for the dependency nodes. # Result `swift build/run` now support passing trait configuration. --------- Co-authored-by: Max Desiatov <[email protected]>
1 parent 6e2a84f commit dbeda87

23 files changed

+516
-56
lines changed

Sources/Build/BuildOperation.swift

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
5555
/// The path to scratch space (.build) directory.
5656
let scratchDirectory: AbsolutePath
5757

58+
/// The trait configuration for this build operation.
59+
private let traitConfiguration: TraitConfiguration?
60+
5861
/// The llbuild build system reference previously created
5962
/// via `createBuildSystem` call.
6063
private var current: (buildSystem: SPMLLBuild.BuildSystem, tracker: LLBuildProgressTracker)?
@@ -109,13 +112,49 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
109112
/// Map of root package identities by target names which are declared in them.
110113
private let rootPackageIdentityByTargetName: [String: PackageIdentity]
111114

112-
public init(
115+
public convenience init(
116+
productsBuildParameters: BuildParameters,
117+
toolsBuildParameters: BuildParameters,
118+
cacheBuildManifest: Bool,
119+
packageGraphLoader: @escaping () throws -> ModulesGraph,
120+
pluginConfiguration: PluginConfiguration? = .none,
121+
scratchDirectory: AbsolutePath,
122+
additionalFileRules: [FileRuleDescription],
123+
pkgConfigDirectories: [AbsolutePath],
124+
dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]],
125+
targetsByRootPackageIdentity: [PackageIdentity: [String]],
126+
outputStream: OutputByteStream,
127+
logLevel: Basics.Diagnostic.Severity,
128+
fileSystem: Basics.FileSystem,
129+
observabilityScope: ObservabilityScope
130+
) {
131+
self.init(
132+
productsBuildParameters: productsBuildParameters,
133+
toolsBuildParameters: toolsBuildParameters,
134+
cacheBuildManifest: cacheBuildManifest,
135+
packageGraphLoader: packageGraphLoader,
136+
pluginConfiguration: pluginConfiguration,
137+
scratchDirectory: scratchDirectory,
138+
traitConfiguration: nil,
139+
additionalFileRules: additionalFileRules,
140+
pkgConfigDirectories: pkgConfigDirectories,
141+
dependenciesByRootPackageIdentity: dependenciesByRootPackageIdentity,
142+
targetsByRootPackageIdentity: targetsByRootPackageIdentity,
143+
outputStream: outputStream,
144+
logLevel: logLevel,
145+
fileSystem: fileSystem,
146+
observabilityScope: observabilityScope
147+
)
148+
}
149+
150+
package init(
113151
productsBuildParameters: BuildParameters,
114152
toolsBuildParameters: BuildParameters,
115153
cacheBuildManifest: Bool,
116154
packageGraphLoader: @escaping () throws -> ModulesGraph,
117155
pluginConfiguration: PluginConfiguration? = .none,
118156
scratchDirectory: AbsolutePath,
157+
traitConfiguration: TraitConfiguration?,
119158
additionalFileRules: [FileRuleDescription],
120159
pkgConfigDirectories: [AbsolutePath],
121160
dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]],
@@ -139,6 +178,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
139178
self.additionalFileRules = additionalFileRules
140179
self.pluginConfiguration = pluginConfiguration
141180
self.scratchDirectory = scratchDirectory
181+
self.traitConfiguration = traitConfiguration
142182
self.pkgConfigDirectories = pkgConfigDirectories
143183
self.dependenciesByRootPackageIdentity = dependenciesByRootPackageIdentity
144184
self.rootPackageIdentityByTargetName = (try? Dictionary<String, PackageIdentity>(throwingUniqueKeysWithValues: targetsByRootPackageIdentity.lazy.flatMap { e in e.value.map { ($0, e.key) } })) ?? [:]
@@ -171,7 +211,13 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
171211
throw InternalError("could not find build descriptor at \(buildDescriptionPath)")
172212
}
173213
// return the build description that's on disk.
174-
return try BuildDescription.load(fileSystem: self.fileSystem, path: buildDescriptionPath)
214+
let buildDescription = try BuildDescription.load(fileSystem: self.fileSystem, path: buildDescriptionPath)
215+
216+
// We need to check that the build has same traits enabled for the cached build operation
217+
// match otherwise we have to re-plan.
218+
if buildDescription.traitConfiguration == self.traitConfiguration {
219+
return buildDescription
220+
}
175221
}
176222
} catch {
177223
// since caching is an optimization, warn about failing to load the cached version
@@ -614,6 +660,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
614660
cacheBuildManifest: false,
615661
packageGraphLoader: { graph },
616662
scratchDirectory: pluginsBuildParameters.dataPath,
663+
traitConfiguration: self.traitConfiguration,
617664
additionalFileRules: self.additionalFileRules,
618665
pkgConfigDirectories: self.pkgConfigDirectories,
619666
dependenciesByRootPackageIdentity: [:],
@@ -737,6 +784,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
737784

738785
let (buildDescription, buildManifest) = try BuildDescription.create(
739786
with: plan,
787+
traitConfiguration: self.traitConfiguration,
740788
disableSandboxForPluginCommands: self.pluginConfiguration?.disableSandbox ?? false,
741789
fileSystem: self.fileSystem,
742790
observabilityScope: self.observabilityScope
@@ -906,6 +954,7 @@ extension BuildOperation {
906954
extension BuildDescription {
907955
static func create(
908956
with plan: BuildPlan,
957+
traitConfiguration: TraitConfiguration?,
909958
disableSandboxForPluginCommands: Bool,
910959
fileSystem: Basics.FileSystem,
911960
observabilityScope: ObservabilityScope
@@ -932,7 +981,8 @@ extension BuildDescription {
932981
testEntryPointCommands: testEntryPointCommands,
933982
copyCommands: copyCommands,
934983
writeCommands: writeCommands,
935-
pluginDescriptions: plan.pluginDescriptions
984+
pluginDescriptions: plan.pluginDescriptions,
985+
traitConfiguration: traitConfiguration
936986
)
937987
try fileSystem.createDirectory(
938988
plan.destinationBuildParameters.buildDescriptionPath.parentDirectory,

Sources/Build/LLBuildDescription.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Basics
1414
import Foundation
1515
import LLBuildManifest
1616
import SPMBuildCore
17+
import PackageGraph
1718

1819
import struct TSCBasic.ByteString
1920

@@ -60,6 +61,9 @@ public struct BuildDescription: Codable {
6061
/// Distilled information about any plugins defined in the package.
6162
let pluginDescriptions: [PluginBuildDescription]
6263

64+
/// The enabled traits of the root package.
65+
let traitConfiguration: TraitConfiguration?
66+
6367
public init(
6468
plan: BuildPlan,
6569
swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool],
@@ -69,6 +73,30 @@ public struct BuildDescription: Codable {
6973
copyCommands: [LLBuildManifest.CmdName: CopyTool],
7074
writeCommands: [LLBuildManifest.CmdName: WriteAuxiliaryFile],
7175
pluginDescriptions: [PluginBuildDescription]
76+
) throws {
77+
try self.init(
78+
plan: plan,
79+
swiftCommands: swiftCommands,
80+
swiftFrontendCommands: swiftFrontendCommands,
81+
testDiscoveryCommands: testDiscoveryCommands,
82+
testEntryPointCommands: testEntryPointCommands,
83+
copyCommands: copyCommands,
84+
writeCommands: writeCommands,
85+
pluginDescriptions: pluginDescriptions,
86+
traitConfiguration: nil
87+
)
88+
}
89+
90+
package init(
91+
plan: BuildPlan,
92+
swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool],
93+
swiftFrontendCommands: [LLBuildManifest.CmdName: SwiftFrontendTool],
94+
testDiscoveryCommands: [LLBuildManifest.CmdName: TestDiscoveryTool],
95+
testEntryPointCommands: [LLBuildManifest.CmdName: TestEntryPointTool],
96+
copyCommands: [LLBuildManifest.CmdName: CopyTool],
97+
writeCommands: [LLBuildManifest.CmdName: WriteAuxiliaryFile],
98+
pluginDescriptions: [PluginBuildDescription],
99+
traitConfiguration: TraitConfiguration?
72100
) throws {
73101
self.swiftCommands = swiftCommands
74102
self.swiftFrontendCommands = swiftFrontendCommands
@@ -78,6 +106,7 @@ public struct BuildDescription: Codable {
78106
self.writeCommands = writeCommands
79107
self.explicitTargetDependencyImportCheckingMode = plan.destinationBuildParameters.driverParameters
80108
.explicitTargetDependencyImportCheckingMode
109+
self.traitConfiguration = traitConfiguration
81110
self.targetDependencyMap = try plan.targets
82111
.reduce(into: [TargetName: [TargetName]]()) { partial, targetBuildDescription in
83112
let deps = try targetBuildDescription.target.recursiveDependencies(

Sources/Commands/PackageCommands/APIDiff.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ struct APIDiff: SwiftCommand {
6767
help: "One or more targets to include in the API comparison. If present, only the specified targets (and any products specified using `--products`) will be compared.")
6868
var targets: [String] = []
6969

70+
@OptionGroup(visibility: .hidden)
71+
package var traits: TraitOptions
72+
7073
@Option(name: .customLong("baseline-dir"),
7174
help: "The path to a directory used to store API baseline files. If unspecified, a temporary directory will be used.")
7275
var overrideBaselineDir: AbsolutePath?
@@ -83,7 +86,11 @@ struct APIDiff: SwiftCommand {
8386
let baselineRevision = try repository.resolveRevision(identifier: treeish)
8487

8588
// We turn build manifest caching off because we need the build plan.
86-
let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
89+
let buildSystem = try swiftCommandState.createBuildSystem(
90+
explicitBuildSystem: .native,
91+
traitConfiguration: .init(traitOptions: self.traits),
92+
cacheBuildManifest: false
93+
)
8794

8895
let packageGraph = try buildSystem.getPackageGraph()
8996
let modulesToDiff = try determineModulesToDiff(

Sources/Commands/PackageCommands/DumpCommands.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ struct DumpSymbolGraph: SwiftCommand {
4747
// Build the current package.
4848
//
4949
// We turn build manifest caching off because we need the build plan.
50-
let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
50+
let buildSystem = try swiftCommandState.createBuildSystem(
51+
explicitBuildSystem: .native,
52+
// We are enabling all traits for dumping the symbol graph.
53+
traitConfiguration: .init(enableAllTraits: true),
54+
cacheBuildManifest: false
55+
)
5156
try buildSystem.build()
5257

5358
// Configure the symbol graph extractor.

Sources/Commands/PackageCommands/InstalledPackages.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ extension SwiftPackageCommand {
8080
throw StringError("\(productToInstall.name) is already installed at \(existingPkg.path)")
8181
}
8282

83-
try tool.createBuildSystem(explicitProduct: productToInstall.name)
83+
try tool.createBuildSystem(explicitProduct: productToInstall.name, traitConfiguration: .init())
8484
.build(subset: .product(productToInstall.name))
8585

8686
let binPath = try tool.productsBuildParameters.buildPath.appending(component: productToInstall.name)

Sources/Commands/PackageCommands/PluginCommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ struct PluginCommand: SwiftCommand {
321321
// Build or bring up-to-date any executable host-side tools on which this plugin depends. Add them and any binary dependencies to the tool-names-to-path map.
322322
let buildSystem = try swiftCommandState.createBuildSystem(
323323
explicitBuildSystem: .native,
324+
traitConfiguration: .init(),
324325
cacheBuildManifest: false,
325326
productsBuildParameters: swiftCommandState.productsBuildParameters,
326327
toolsBuildParameters: buildParameters,

Sources/Commands/Snippets/Cards/SnippetCard.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct SnippetCard: Card {
9393

9494
func runExample() throws {
9595
print("Building '\(snippet.path)'\n")
96-
let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: snippet.name)
96+
let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: snippet.name, traitConfiguration: .init())
9797
try buildSystem.build(subset: .product(snippet.name))
9898
let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: snippet.name)
9999
if let exampleTarget = try buildSystem.getPackageGraph().module(for: snippet.name, destination: .destination) {

Sources/Commands/SwiftBuildCommand.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ struct BuildCommandOptions: ParsableArguments {
9797
@Option(help: "Build the specified product")
9898
var product: String?
9999

100+
/// Specifies the traits to build.
101+
@OptionGroup(visibility: .hidden)
102+
package var traits: TraitOptions
103+
100104
/// If should link the Swift stdlib statically.
101105
@Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically")
102106
public var shouldLinkStaticSwiftStdlib: Bool = false
@@ -140,7 +144,10 @@ public struct SwiftBuildCommand: AsyncSwiftCommand {
140144

141145
if options.printManifestGraphviz {
142146
// FIXME: Doesn't seem ideal that we need an explicit build operation, but this concretely uses the `LLBuildManifest`.
143-
guard let buildOperation = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native) as? BuildOperation else {
147+
guard let buildOperation = try swiftCommandState.createBuildSystem(
148+
explicitBuildSystem: .native,
149+
traitConfiguration: .init(traitOptions: self.options.traits)
150+
) as? BuildOperation else {
144151
throw StringError("asked for native build system but did not get it")
145152
}
146153
let buildManifest = try buildOperation.getBuildManifest()
@@ -198,6 +205,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand {
198205
) throws {
199206
let buildSystem = try swiftCommandState.createBuildSystem(
200207
explicitProduct: options.product,
208+
traitConfiguration: .init(traitOptions: self.options.traits),
201209
shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib,
202210
productsBuildParameters: productsBuildParameters,
203211
toolsBuildParameters: toolsBuildParameters,

Sources/Commands/SwiftRunCommand.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ struct RunCommandOptions: ParsableArguments {
8383
@Argument(help: "The executable to run", completion: .shellCommand("swift package completion-tool list-executables"))
8484
var executable: String?
8585

86+
/// Specifies the traits to build the product with.
87+
@OptionGroup(visibility: .hidden)
88+
package var traits: TraitOptions
89+
8690
/// The arguments to pass to the executable.
8791
@Argument(parsing: .captureForPassthrough,
8892
help: "The arguments to pass to the executable")
@@ -130,6 +134,7 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
130134
// FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the REPL. rdar://86112934
131135
let buildSystem = try swiftCommandState.createBuildSystem(
132136
explicitBuildSystem: .native,
137+
traitConfiguration: .init(traitOptions: self.options.traits),
133138
cacheBuildManifest: false,
134139
packageGraphLoader: graphLoader
135140
)
@@ -149,7 +154,10 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
149154

150155
case .debugger:
151156
do {
152-
let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: options.executable)
157+
let buildSystem = try swiftCommandState.createBuildSystem(
158+
explicitProduct: options.executable,
159+
traitConfiguration: .init(traitOptions: self.options.traits)
160+
)
153161
let productName = try findProductName(in: buildSystem.getPackageGraph())
154162
if options.shouldBuildTests {
155163
try buildSystem.build(subset: .allIncludingTests)
@@ -191,7 +199,10 @@ public struct SwiftRunCommand: AsyncSwiftCommand {
191199
}
192200

193201
do {
194-
let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: options.executable)
202+
let buildSystem = try swiftCommandState.createBuildSystem(
203+
explicitProduct: options.executable,
204+
traitConfiguration: .init(traitOptions: self.options.traits)
205+
)
195206
let productName = try findProductName(in: buildSystem.getPackageGraph())
196207
if options.shouldBuildTests {
197208
try buildSystem.build(subset: .allIncludingTests)

Sources/Commands/SwiftTestCommand.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,8 @@ private func buildTestsIfNeeded(
13771377
testProduct: String?
13781378
) throws -> [BuiltTestProduct] {
13791379
let buildSystem = try swiftCommandState.createBuildSystem(
1380+
// TODO: Will support traits in test in a follow up PR
1381+
traitConfiguration: .init(),
13801382
productsBuildParameters: productsBuildParameters,
13811383
toolsBuildParameters: toolsBuildParameters
13821384
)

Sources/Commands/Utilities/APIDigester.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ struct APIDigesterBaselineDumper {
139139
// FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the APIDigester. rdar://86112934
140140
let buildSystem = try swiftCommandState.createBuildSystem(
141141
explicitBuildSystem: .native,
142+
traitConfiguration: .init(),
142143
cacheBuildManifest: false,
143144
productsBuildParameters: productsBuildParameters,
144145
toolsBuildParameters: toolsBuildParameters,

Sources/Commands/Utilities/PluginDelegate.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ final class PluginDelegate: PluginInvocationDelegate {
161161
let buildSystem = try swiftCommandState.createBuildSystem(
162162
explicitBuildSystem: .native,
163163
explicitProduct: explicitProduct,
164+
traitConfiguration: .init(),
164165
cacheBuildManifest: false,
165166
productsBuildParameters: buildParameters,
166167
outputStream: outputStream,
@@ -223,7 +224,10 @@ final class PluginDelegate: PluginInvocationDelegate {
223224
var toolsBuildParameters = try swiftCommandState.toolsBuildParameters
224225
toolsBuildParameters.testingParameters.enableTestability = true
225226
toolsBuildParameters.testingParameters.enableCodeCoverage = parameters.enableCodeCoverage
226-
let buildSystem = try swiftCommandState.createBuildSystem(toolsBuildParameters: toolsBuildParameters)
227+
let buildSystem = try swiftCommandState.createBuildSystem(
228+
traitConfiguration: .init(),
229+
toolsBuildParameters: toolsBuildParameters
230+
)
227231
try buildSystem.build(subset: .allIncludingTests)
228232

229233
// Clean out the code coverage directory that may contain stale `profraw` files from a previous run of
@@ -381,7 +385,11 @@ final class PluginDelegate: PluginInvocationDelegate {
381385
// while building.
382386

383387
// Create a build system for building the target., skipping the the cache because we need the build plan.
384-
let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
388+
let buildSystem = try swiftCommandState.createBuildSystem(
389+
explicitBuildSystem: .native,
390+
traitConfiguration: .init(),
391+
cacheBuildManifest: false
392+
)
385393

386394
// Find the target in the build operation's package graph; it's an error if we don't find it.
387395
let packageGraph = try buildSystem.getPackageGraph()

0 commit comments

Comments
 (0)