Skip to content

Commit 033295d

Browse files
committed
Integrate trait handling into module loading
# Motivation In my previous PR I landed the APIs for package traits. We now have to take this information into consideration when evaluating the module graph. # Modification This PR implements the various trait features in the module graph loading stage. Including resolving optional dependencies, enabled traits of dependencies, build settings based on traits and passing defines for traits. This PR also contains an exhaustive test fixtures which we can use to check if all trait functionality is working as expected.
1 parent d4d17b3 commit 033295d

20 files changed

+682
-77
lines changed

Sources/PackageDescription/PackageDependency.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ extension Package {
5353
/// The dependencies traits configuration.
5454
@_spi(ExperimentalTraits)
5555
@available(_PackageDescription, introduced: 999.0)
56-
public let traits: Set<Trait>?
56+
public let traits: Set<Trait>
5757

5858
/// The name of the dependency.
5959
///
@@ -145,7 +145,7 @@ extension Package {
145145

146146
init(kind: Kind, traits: Set<Trait>?) {
147147
self.kind = kind
148-
self.traits = traits
148+
self.traits = traits ?? [.defaults]
149149
}
150150

151151
convenience init(

Sources/PackageDescription/PackageDescriptionSerializationConversion.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ extension Serialization.PackageDependency {
172172
init(_ dependency: PackageDescription.Package.Dependency) {
173173
self.kind = .init(dependency.kind)
174174
self.moduleAliases = dependency.moduleAliases
175-
self.traits = Set(dependency.traits?.map { Serialization.PackageDependency.Trait.init($0) } ?? [])
175+
self.traits = Set(dependency.traits.map { Serialization.PackageDependency.Trait.init($0) })
176176
}
177177
}
178178

Sources/PackageGraph/GraphLoadingNode.swift

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,58 @@ public struct GraphLoadingNode: Equatable, Hashable {
2929
/// The product filter applied to the package.
3030
public let productFilter: ProductFilter
3131

32-
public init(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter) {
32+
/// The enabled traits for this package.
33+
public var enabledTraits: Set<String>
34+
35+
public init(
36+
identity: PackageIdentity,
37+
manifest: Manifest,
38+
productFilter: ProductFilter,
39+
enabledTraits: Set<String>?
40+
) throws {
3341
self.identity = identity
3442
self.manifest = manifest
3543
self.productFilter = productFilter
44+
45+
// We are going to calculate which traits are actually enabled for a node here. To do this
46+
// we have to check if default traits should be used and then flatten all the enabled traits.
47+
for trait in enabledTraits ?? [] {
48+
if trait == "defaults" {
49+
// The defaults trait is special and every package has it
50+
continue
51+
}
52+
// Check if the enabled trait is a valid trait
53+
if self.manifest.traits.first(where: { $0.name == trait }) == nil {
54+
// The enabled trait is invalid
55+
throw ModuleError.invalidTrait(package: identity, trait: trait)
56+
}
57+
}
58+
59+
// This the point where we flatten the enabled traits and resolve the recursive traits
60+
var recursiveEnabledTraits = enabledTraits ?? []
61+
62+
// We have to enable all default traits if no traits are enabled or the defaults are explicitly enabled
63+
let areDefaultsEnabled = enabledTraits?.contains("defaults") ?? false
64+
if enabledTraits == nil || areDefaultsEnabled {
65+
recursiveEnabledTraits.formUnion(self.manifest.traits.lazy.filter { $0.isDefault }.map { $0.name })
66+
}
67+
68+
while true {
69+
let flattendEnabledTraits = Set(self.manifest.traits
70+
.lazy
71+
.filter { recursiveEnabledTraits.contains($0.name) }
72+
.map { $0.enabledTraits }
73+
.joined()
74+
)
75+
let newRecursiveEnabledTraits = recursiveEnabledTraits.union(flattendEnabledTraits)
76+
if newRecursiveEnabledTraits.count == recursiveEnabledTraits.count {
77+
break
78+
} else {
79+
recursiveEnabledTraits = newRecursiveEnabledTraits
80+
}
81+
}
82+
83+
self.enabledTraits = recursiveEnabledTraits
3684
}
3785

3886
/// Returns the dependencies required by this node.
@@ -51,7 +99,3 @@ extension GraphLoadingNode: CustomStringConvertible {
5199
}
52100
}
53101
}
54-
55-
extension GraphLoadingNode: Identifiable {
56-
public var id: PackageIdentity { self.identity }
57-
}

Sources/PackageGraph/ModulesGraph+Loading.swift

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ extension ModulesGraph {
3737
fileSystem: FileSystem,
3838
observabilityScope: ObservabilityScope
3939
) throws -> ModulesGraph {
40-
4140
let observabilityScope = observabilityScope.makeChildScope(description: "Loading Package Graph")
4241

4342
// Create a map of the manifests, keyed by their identity.
@@ -51,43 +50,64 @@ extension ModulesGraph {
5150
let rootDependencies = Set(root.dependencies.compactMap{
5251
manifestMap[$0.identity]?.manifest
5352
})
54-
let rootManifestNodes = root.packages.map { identity, package in
55-
GraphLoadingNode(identity: identity, manifest: package.manifest, productFilter: .everything)
53+
54+
let rootManifestNodes = try root.packages.map { identity, package in
55+
// We are going to enable all default traits of the root manifests.
56+
// In a future PR we make this overridable by CLI options.
57+
let enabledTraits = package.manifest.traits.lazy.filter { $0.isDefault }.map { $0.name }
58+
return try GraphLoadingNode(
59+
identity: identity,
60+
manifest: package.manifest,
61+
productFilter: .everything,
62+
enabledTraits: Set(enabledTraits)
63+
)
5664
}
57-
let rootDependencyNodes = root.dependencies.lazy.compactMap { dependency in
58-
manifestMap[dependency.identity].map {
59-
GraphLoadingNode(
65+
let rootDependencyNodes = try root.dependencies.lazy.compactMap { dependency in
66+
try manifestMap[dependency.identity].map {
67+
try GraphLoadingNode(
6068
identity: dependency.identity,
6169
manifest: $0.manifest,
62-
productFilter: dependency.productFilter
70+
productFilter: dependency.productFilter,
71+
enabledTraits: dependency.traits.flatMap { Set($0.map { $0.name }) }
6372
)
6473
}
6574
}
6675
let inputManifests = (rootManifestNodes + rootDependencyNodes).map {
67-
KeyedPair($0, key: $0.id)
76+
KeyedPair($0, key: $0.identity)
6877
}
6978

7079
// Collect the manifests for which we are going to build packages.
71-
var allNodes = [GraphLoadingNode]()
80+
var allNodes = OrderedDictionary<PackageIdentity, GraphLoadingNode>()
7281

7382
let nodeSuccessorProvider = { (node: KeyedPair<GraphLoadingNode, PackageIdentity>) in
74-
node.item.requiredDependencies.compactMap { dependency in
75-
manifestMap[dependency.identity].map { manifest, _ in
76-
KeyedPair(
77-
GraphLoadingNode(
78-
identity: dependency.identity,
79-
manifest: manifest,
80-
productFilter: dependency.productFilter
81-
),
82-
key: dependency.identity
83-
)
83+
return try node.item.requiredDependencies.compactMap { dependency in
84+
return try manifestMap[dependency.identity].map { manifest, _ in
85+
// We are going to check the conditionally enabled traits here and enable them if
86+
// required. This checks the current node and then enables the conditional
87+
// dependencies of the dependency node.
88+
let enabledTraits = dependency.traits?.filter {
89+
guard let conditionTraits = $0.condition?.traits else {
90+
return true
91+
}
92+
return !conditionTraits.intersection(node.item.enabledTraits).isEmpty
93+
}.map { $0.name }
94+
95+
return try KeyedPair(
96+
GraphLoadingNode(
97+
identity: dependency.identity,
98+
manifest: manifest,
99+
productFilter: dependency.productFilter,
100+
enabledTraits: enabledTraits.flatMap { Set($0) }
101+
),
102+
key: dependency.identity
103+
)
84104
}
85105
}
86106
}
87107

88108
// Package dependency cycles feature is gated on tools version 6.0.
89109
if !root.manifests.allSatisfy({ $1.toolsVersion >= .v6_0 }) {
90-
if let cycle = findCycle(inputManifests, successors: nodeSuccessorProvider) {
110+
if let cycle = try findCycle(inputManifests, successors: nodeSuccessorProvider) {
91111
let path = (cycle.path + cycle.cycle).map(\.item.manifest)
92112
observabilityScope.emit(PackageGraphError.dependencyCycleDetected(
93113
path: path, cycle: cycle.cycle[0].item.manifest
@@ -104,18 +124,19 @@ extension ModulesGraph {
104124
}
105125

106126
// Cycles in dependencies don't matter as long as there are no target cycles between packages.
107-
depthFirstSearch(
127+
try depthFirstSearch(
108128
inputManifests,
109129
successors: nodeSuccessorProvider
110130
) {
111-
allNodes.append($0.item)
112-
} onDuplicate: { _,_ in
113-
// no de-duplication is required.
131+
allNodes[$0.key] = $0.item
132+
} onDuplicate: { first, second in
133+
// We are unifying the enabled traits on duplicate
134+
allNodes[first.key]?.enabledTraits.formUnion(second.item.enabledTraits)
114135
}
115136

116137
// Create the packages.
117138
var manifestToPackage: [Manifest: Package] = [:]
118-
for node in allNodes {
139+
for node in allNodes.values {
119140
let nodeObservabilityScope = observabilityScope.makeChildScope(
120141
description: "loading package \(node.identity)",
121142
metadata: .packageMetadata(identity: node.identity, kind: node.manifest.packageKind)
@@ -139,7 +160,8 @@ extension ModulesGraph {
139160
testEntryPointPath: testEntryPointPath,
140161
createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false,
141162
fileSystem: fileSystem,
142-
observabilityScope: nodeObservabilityScope
163+
observabilityScope: nodeObservabilityScope,
164+
enabledTraits: node.enabledTraits
143165
)
144166
let package = try builder.construct()
145167
manifestToPackage[manifest] = package
@@ -162,7 +184,7 @@ extension ModulesGraph {
162184

163185
// Resolve dependencies and create resolved packages.
164186
let resolvedPackages = try createResolvedPackages(
165-
nodes: allNodes,
187+
nodes: Array(allNodes.values),
166188
identityResolver: identityResolver,
167189
manifestToPackage: manifestToPackage,
168190
rootManifests: root.manifests,
@@ -273,7 +295,7 @@ private func createResolvedPackages(
273295
) throws -> IdentifiableSet<ResolvedPackage> {
274296

275297
// Create package builder objects from the input manifests.
276-
let packageBuilders: [ResolvedPackageBuilder] = nodes.compactMap{ node in
298+
let packageBuilders: [ResolvedPackageBuilder] = nodes.compactMap { node in
277299
guard let package = manifestToPackage[node.manifest] else {
278300
return nil
279301
}
@@ -283,6 +305,7 @@ private func createResolvedPackages(
283305
return ResolvedPackageBuilder(
284306
package,
285307
productFilter: node.productFilter,
308+
enabledTraits: node.enabledTraits,
286309
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts,
287310
allowedToOverride: allowedToOverride,
288311
platformVersionProvider: platformVersionProvider
@@ -315,8 +338,9 @@ private func createResolvedPackages(
315338
var dependenciesByNameForTargetDependencyResolution = [String: ResolvedPackageBuilder]()
316339
var dependencyNamesForTargetDependencyResolutionOnly = [PackageIdentity: String]()
317340

318-
// Establish the manifest-declared package dependencies.
319-
package.manifest.dependenciesRequired(for: packageBuilder.productFilter).forEach { dependency in
341+
package.manifest.dependenciesRequired(
342+
for: packageBuilder.productFilter
343+
).forEach { dependency in
320344
let dependencyPackageRef = dependency.packageRef
321345

322346
// Otherwise, look it up by its identity.
@@ -530,6 +554,13 @@ private func createResolvedPackages(
530554

531555
// Establish product dependencies.
532556
for case .product(let productRef, let conditions) in targetBuilder.target.dependencies {
557+
if let traitCondition = conditions.compactMap({ $0.traitCondition }).first {
558+
if packageBuilder.enabledTraits.intersection(traitCondition.traits).isEmpty {
559+
/// If we land here non of the traits required to enable this depenendcy has been enabled.
560+
continue
561+
}
562+
}
563+
533564
// Find the product in this package's dependency products.
534565
// Look it up by ID if module aliasing is used, otherwise by name.
535566
let product = lookupByProductIDs ? productDependencyMap[productRef.identity] : productDependencyMap[productRef.name]
@@ -1054,6 +1085,9 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
10541085
/// The products in this package.
10551086
var products: [ResolvedProductBuilder] = []
10561087

1088+
/// The enabled traits of this package.
1089+
var enabledTraits: Set<String> = []
1090+
10571091
/// The dependencies of this package.
10581092
var dependencies: [ResolvedPackageBuilder] = []
10591093

@@ -1074,12 +1108,14 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
10741108
init(
10751109
_ package: Package,
10761110
productFilter: ProductFilter,
1111+
enabledTraits: Set<String>,
10771112
isAllowedToVendUnsafeProducts: Bool,
10781113
allowedToOverride: Bool,
10791114
platformVersionProvider: PlatformVersionProvider
10801115
) {
10811116
self.package = package
10821117
self.productFilter = productFilter
1118+
self.enabledTraits = enabledTraits
10831119
self.isAllowedToVendUnsafeProducts = isAllowedToVendUnsafeProducts
10841120
self.allowedToOverride = allowedToOverride
10851121
self.platformVersionProvider = platformVersionProvider
@@ -1095,6 +1131,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
10951131
defaultLocalization: self.defaultLocalization,
10961132
supportedPlatforms: self.supportedPlatforms,
10971133
dependencies: self.dependencies.map { $0.package.identity },
1134+
enabledTraits: self.enabledTraits,
10981135
targets: targets,
10991136
products: products,
11001137
registryMetadata: self.registryMetadata,

Sources/PackageGraph/Resolution/ResolvedPackage.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public struct ResolvedPackage {
3939
/// The products produced by the package.
4040
public let products: [ResolvedProduct]
4141

42+
/// The enabled traits of this package.
43+
public let enabledTraits: Set<String>
44+
4245
/// The dependencies of the package.
4346
public let dependencies: [PackageIdentity]
4447

@@ -58,6 +61,7 @@ public struct ResolvedPackage {
5861
defaultLocalization: String?,
5962
supportedPlatforms: [SupportedPlatform],
6063
dependencies: [PackageIdentity],
64+
enabledTraits: Set<String>,
6165
targets: IdentifiableSet<ResolvedModule>,
6266
products: [ResolvedProduct],
6367
registryMetadata: RegistryReleaseMetadata?,
@@ -71,6 +75,7 @@ public struct ResolvedPackage {
7175
self.supportedPlatforms = supportedPlatforms
7276
self.registryMetadata = registryMetadata
7377
self.platformVersionProvider = platformVersionProvider
78+
self.enabledTraits = enabledTraits
7479
}
7580

7681
public func getSupportedPlatform(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform {

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ extension MappablePackageDependency {
638638
path: path
639639
),
640640
productFilter: .everything,
641-
traits: Set(seed.traits?.map { PackageDependency.Trait.init($0) } ?? [])
641+
traits: seed.traits.flatMap { Set($0.map { PackageDependency.Trait.init($0) } ) }
642642
)
643643
case .sourceControl(let name, let location, let requirement):
644644
self.init(
@@ -649,7 +649,7 @@ extension MappablePackageDependency {
649649
requirement: .init(requirement)
650650
),
651651
productFilter: .everything,
652-
traits: Set(seed.traits?.map { PackageDependency.Trait.init($0) } ?? [])
652+
traits: seed.traits.flatMap { Set($0.map { PackageDependency.Trait.init($0) } ) }
653653
)
654654
case .registry(let id, let requirement):
655655
self.init(
@@ -659,7 +659,7 @@ extension MappablePackageDependency {
659659
requirement: .init(requirement)
660660
),
661661
productFilter: .everything,
662-
traits: Set(seed.traits?.map { PackageDependency.Trait.init($0) } ?? [])
662+
traits: seed.traits.flatMap { Set($0.map { PackageDependency.Trait.init($0) } ) }
663663
)
664664
}
665665
}

0 commit comments

Comments
 (0)