Skip to content

Commit 4451034

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 4451034

17 files changed

+664
-59
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: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,57 @@ 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 valid for all packages.
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+
// Add all default traits
63+
if enabledTraits.contains("defaults") {
64+
recursiveEnabledTraits.formUnion(self.manifest.traits.lazy.filter { $0.isDefault }.map { $0.name })
65+
}
66+
67+
while true {
68+
let flattendEnabledTraits = Set(self.manifest.traits
69+
.lazy
70+
.filter { recursiveEnabledTraits.contains($0.name) }
71+
.map { $0.enabledTraits }
72+
.joined()
73+
)
74+
let newRecursiveEnabledTraits = recursiveEnabledTraits.union(flattendEnabledTraits)
75+
if newRecursiveEnabledTraits.count == recursiveEnabledTraits.count {
76+
break
77+
} else {
78+
recursiveEnabledTraits = newRecursiveEnabledTraits
79+
}
80+
}
81+
82+
self.enabledTraits = recursiveEnabledTraits
3683
}
3784

3885
/// Returns the dependencies required by this node.
@@ -51,7 +98,3 @@ extension GraphLoadingNode: CustomStringConvertible {
5198
}
5299
}
53100
}
54-
55-
extension GraphLoadingNode: Identifiable {
56-
public var id: PackageIdentity { self.identity }
57-
}

Sources/PackageGraph/ModulesGraph+Loading.swift

Lines changed: 68 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: Set(dependency.traits.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: Set(enabledTraits)
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,20 @@ 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+
print(first.item.identity, first.item.enabledTraits, second.item.enabledTraits)
135+
allNodes[first.key]?.enabledTraits.formUnion(second.item.enabledTraits)
114136
}
115137

116138
// Create the packages.
117139
var manifestToPackage: [Manifest: Package] = [:]
118-
for node in allNodes {
140+
for node in allNodes.values {
119141
let nodeObservabilityScope = observabilityScope.makeChildScope(
120142
description: "loading package \(node.identity)",
121143
metadata: .packageMetadata(identity: node.identity, kind: node.manifest.packageKind)
@@ -139,7 +161,8 @@ extension ModulesGraph {
139161
testEntryPointPath: testEntryPointPath,
140162
createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false,
141163
fileSystem: fileSystem,
142-
observabilityScope: nodeObservabilityScope
164+
observabilityScope: nodeObservabilityScope,
165+
enabledTraits: node.enabledTraits
143166
)
144167
let package = try builder.construct()
145168
manifestToPackage[manifest] = package
@@ -162,7 +185,7 @@ extension ModulesGraph {
162185

163186
// Resolve dependencies and create resolved packages.
164187
let resolvedPackages = try createResolvedPackages(
165-
nodes: allNodes,
188+
nodes: Array(allNodes.values),
166189
identityResolver: identityResolver,
167190
manifestToPackage: manifestToPackage,
168191
rootManifests: root.manifests,
@@ -273,7 +296,7 @@ private func createResolvedPackages(
273296
) throws -> IdentifiableSet<ResolvedPackage> {
274297

275298
// Create package builder objects from the input manifests.
276-
let packageBuilders: [ResolvedPackageBuilder] = nodes.compactMap{ node in
299+
let packageBuilders: [ResolvedPackageBuilder] = nodes.compactMap { node in
277300
guard let package = manifestToPackage[node.manifest] else {
278301
return nil
279302
}
@@ -283,6 +306,7 @@ private func createResolvedPackages(
283306
return ResolvedPackageBuilder(
284307
package,
285308
productFilter: node.productFilter,
309+
enabledTraits: node.enabledTraits,
286310
isAllowedToVendUnsafeProducts: isAllowedToVendUnsafeProducts,
287311
allowedToOverride: allowedToOverride,
288312
platformVersionProvider: platformVersionProvider
@@ -315,8 +339,9 @@ private func createResolvedPackages(
315339
var dependenciesByNameForTargetDependencyResolution = [String: ResolvedPackageBuilder]()
316340
var dependencyNamesForTargetDependencyResolutionOnly = [PackageIdentity: String]()
317341

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

322347
// Otherwise, look it up by its identity.
@@ -530,6 +555,13 @@ private func createResolvedPackages(
530555

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

1089+
/// The enabled traits of this package.
1090+
var enabledTraits: Set<String> = []
1091+
10571092
/// The dependencies of this package.
10581093
var dependencies: [ResolvedPackageBuilder] = []
10591094

@@ -1074,12 +1109,14 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
10741109
init(
10751110
_ package: Package,
10761111
productFilter: ProductFilter,
1112+
enabledTraits: Set<String>,
10771113
isAllowedToVendUnsafeProducts: Bool,
10781114
allowedToOverride: Bool,
10791115
platformVersionProvider: PlatformVersionProvider
10801116
) {
10811117
self.package = package
10821118
self.productFilter = productFilter
1119+
self.enabledTraits = enabledTraits
10831120
self.isAllowedToVendUnsafeProducts = isAllowedToVendUnsafeProducts
10841121
self.allowedToOverride = allowedToOverride
10851122
self.platformVersionProvider = platformVersionProvider
@@ -1095,6 +1132,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder<ResolvedPackage> {
10951132
defaultLocalization: self.defaultLocalization,
10961133
supportedPlatforms: self.supportedPlatforms,
10971134
dependencies: self.dependencies.map { $0.package.identity },
1135+
enabledTraits: self.enabledTraits,
10981136
targets: targets,
10991137
products: products,
11001138
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 {

0 commit comments

Comments
 (0)