Skip to content

Commit 48dceed

Browse files
committed
Module aliases override updates
- Use alias value defined upstream as a key downstream for overriding and chain them - Add pre-chained aliases to targets when needed and show diags Resolves rdar://93445417, rdar://93218209
1 parent 4cf8e7e commit 48dceed

File tree

3 files changed

+211
-51
lines changed

3 files changed

+211
-51
lines changed

Sources/PackageGraph/ModuleAliasTracker.swift

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import PackageModel
2+
import Basics
23

34
// This class helps track module aliases in a package graph and override
45
// upstream alises if needed
@@ -98,7 +99,7 @@ class ModuleAliasTracker {
9899
}
99100
}
100101

101-
func propagateAliases() {
102+
func propagateAliases(observabilityScope: ObservabilityScope) {
102103
// First get the root package ID
103104
var pkgID = childToParentID.first?.key
104105
var rootPkg = pkgID
@@ -111,11 +112,12 @@ class ModuleAliasTracker {
111112
// Propagate and override upstream aliases if needed
112113
var aliasBuffer = [String: ModuleAliasModel]()
113114
propagate(package: rootPkg, aliasBuffer: &aliasBuffer)
115+
114116
// Now merge overriden upstream aliases and add them to
115117
// downstream targets
116118
if let productToAllTargets = idToProductToAllTargets[rootPkg] {
117119
for productID in productToAllTargets.keys {
118-
mergeAliases(productID: productID)
120+
mergeAliases(productID: productID, observabilityScope: observabilityScope)
119121
}
120122
}
121123
}
@@ -130,11 +132,22 @@ class ModuleAliasTracker {
130132
// aliases for targets; if the downstream aliases are applied
131133
// to upstream targets, then they get removed
132134
if aliasBuffer[aliasModel.name] == nil {
133-
// Add a target name as a key. The buffer only tracks
134-
// a target that needs to be renamed, not the depending
135-
// targets which might have multiple target dependencies
136-
// with their aliases, so add a single alias model as value.
137-
aliasBuffer[aliasModel.name] = aliasModel
135+
// First look up if there's an alias to be overriden,
136+
// e.g. upstream has `moduleAliases: [Utils: FooUtils]`,
137+
// and downstream has `moduleAliases: [FooUtils: BarUtils]`,
138+
// then we want to add [Utils: BarUtils] to the buffer
139+
// to track. Both [Utils: FooUtils] and [FooUtils: BarUtils]
140+
// are also added to the buffer.
141+
var next = aliasModel.alias
142+
while let nextValue = aliasBuffer[next] {
143+
next = nextValue.alias
144+
}
145+
if next != aliasModel.alias {
146+
let chainedModel = ModuleAliasModel(name: aliasModel.name, alias: next, originPackage: aliasModel.originPackage, consumingPackage: aliasModel.consumingPackage)
147+
aliasBuffer[aliasModel.name] = chainedModel
148+
} else {
149+
aliasBuffer[aliasModel.name] = aliasModel
150+
}
138151
}
139152
}
140153
}
@@ -151,11 +164,24 @@ class ModuleAliasTracker {
151164
var didAlias = false
152165
for curTarget in targetsForCurProduct {
153166
// Check if curTarget is relevant for aliasing
154-
let canAlias = curTarget.name == aliasModel.name || curTarget.dependencies.contains { $0.name == aliasModel.name }
155-
if canAlias {
167+
let canAliasTarget = curTarget.name == aliasModel.name || curTarget.dependencies.contains { $0.name == aliasModel.name }
168+
if canAliasTarget {
156169
curTarget.addModuleAlias(for: aliasModel.name, as: aliasModel.alias)
157170
didAlias = true
158171
}
172+
// Check if curTarget needs pre-chained aliases
173+
// from its dependency products.
174+
let depProductAliases = curTarget.dependencies.compactMap{$0.product?.moduleAliases}.flatMap{$0}
175+
let depAliasesWithDuplicateKeys = depProductAliases.filter{$0.key == aliasModel.name}
176+
let usePreChainedAliases = (didAlias && !depAliasesWithDuplicateKeys.isEmpty) || depAliasesWithDuplicateKeys.count > 1
177+
178+
if usePreChainedAliases {
179+
for depAlias in depProductAliases {
180+
if let preChainedAlias = aliasBuffer[depAlias.value] {
181+
curTarget.addModuleAlias(for: preChainedAlias.name, as: preChainedAlias.alias)
182+
}
183+
}
184+
}
159185
}
160186
if didAlias {
161187
usedKeys.insert(targetToAlias.name)
@@ -170,17 +196,20 @@ class ModuleAliasTracker {
170196
}
171197
}
172198
}
173-
guard let children = parentToChildIDs[package] else { return }
199+
guard let children = parentToChildIDs[package] else {
200+
aliasBuffer.removeAll()
201+
return
202+
}
174203
for childID in children {
175204
propagate(package: childID, aliasBuffer: &aliasBuffer)
176205
}
177206
}
178207

179208
// Merge overriden upstream aliases and add them to downstream targets
180-
func mergeAliases(productID: String) {
209+
func mergeAliases(productID: String, observabilityScope: ObservabilityScope) {
181210
guard let childProducts = parentToChildProducts[productID] else { return }
182211
for child in childProducts {
183-
mergeAliases(productID: child)
212+
mergeAliases(productID: child, observabilityScope: observabilityScope)
184213
// Filter out targets in the current product with names that are
185214
// aliased with different values in the child products since they
186215
// should either not be aliased or their existing aliases if any
@@ -197,21 +226,25 @@ class ModuleAliasTracker {
197226
relevantTargets.append(contentsOf: directTargets)
198227
relevantTargets.append(contentsOf: directRelevantTargets)
199228
let relevantTargetSet = Set(relevantTargets)
200-
201229
// Used to compare with aliases defined in other child products
202230
// and detect a conflict if any.
203231
let allTargetsInOtherChildProducts = childProducts.filter{$0 != child }.compactMap{productToAllTargets[$0]}.flatMap{$0}
204-
let allTargetNamesInChildProduct = productToAllTargets[child]?.map{$0.name} ?? []
232+
let allTargetNamesInCurChildProduct = productToAllTargets[child]?.map{$0.name} ?? []
205233
for curTarget in relevantTargetSet {
206-
for (nameToBeAliased, aliasInChild) in childTargetsAliases {
207-
// If there are targets in other child products that
208-
// have the same name that's being aliased here, but
209-
// targets in this child product don't, we need to use
210-
// alias values of those targets as they take a precedence
211-
let otherAliasesInChildProducts = allTargetsInOtherChildProducts.filter{$0.name == nameToBeAliased}.compactMap{$0.moduleAliases}.flatMap{$0}.filter{$0.key == nameToBeAliased}
212-
if !otherAliasesInChildProducts.isEmpty,
213-
!allTargetNamesInChildProduct.contains(curTarget.name) {
214-
for (aliasKey, aliasValue) in otherAliasesInChildProducts {
234+
for (nameToBeAliased, aliasInCurChild) in childTargetsAliases {
235+
// Look up targets in other child products that have the
236+
// name `nameToBeAliased` but are aliased with values
237+
// conflicting with `aliasInCurChild`
238+
let aliasesForTargetsRenamedInOtherChildProducts = allTargetsInOtherChildProducts.filter{$0.name == nameToBeAliased}.compactMap{$0.moduleAliases}.flatMap{$0}.filter{$0.key == nameToBeAliased}
239+
let hasConflictingAliasesForTargetsRenamed = aliasesForTargetsRenamedInOtherChildProducts.contains { $0.value != aliasInCurChild }
240+
241+
if hasConflictingAliasesForTargetsRenamed {
242+
// If conflicting aliases are found, wait until
243+
// all conflicts are found and deleted (in the last
244+
// `else` case), so do nothing for now.
245+
} else if !aliasesForTargetsRenamedInOtherChildProducts.isEmpty,
246+
!allTargetNamesInCurChildProduct.contains(curTarget.name) {
247+
for (aliasKey, aliasValue) in aliasesForTargetsRenamedInOtherChildProducts {
215248
// Reset the old alias value with this aliasValue
216249
if curTarget.moduleAliases?[aliasKey] != aliasValue {
217250
curTarget.addModuleAlias(for: aliasKey, as: aliasValue)
@@ -221,20 +254,38 @@ class ModuleAliasTracker {
221254
// If there are no aliases or conflicting aliases
222255
// for the same key defined in other child products,
223256
// those aliases should be removed from this target.
224-
let hasConflict = allTargetsInOtherChildProducts.contains{ otherTarget in
257+
let targetsWithConflictingAliases = allTargetsInOtherChildProducts.filter { otherTarget in
225258
if let otherAlias = otherTarget.moduleAliases?[nameToBeAliased] {
226-
return otherAlias != aliasInChild
259+
return otherAlias != aliasInCurChild
227260
} else {
228261
return otherTarget.name == nameToBeAliased
229262
}
230263
}
231-
if hasConflict {
232-
// If there are aliases, remove as aliasing should
233-
// not be applied
264+
265+
if !targetsWithConflictingAliases.isEmpty {
266+
// If there are conflicting aliases, remove them
267+
// and show a diag message
268+
let conflictingAliases = targetsWithConflictingAliases.compactMap { $0.moduleAliases?[nameToBeAliased] }
269+
var message = conflictingAliases.isEmpty ? "Targets named '\(nameToBeAliased)' found in other dependency products;" : "Conflicting module aliases found \(conflictingAliases + [aliasInCurChild]) for '\(nameToBeAliased)'; "
270+
message += "these will be removed from target '\(curTarget.name)'"
271+
observabilityScope.emit(info: message)
272+
234273
curTarget.removeModuleAlias(for: nameToBeAliased)
235-
} else if curTarget.moduleAliases?[nameToBeAliased] == nil {
236-
// Otherwise add the alias if none exists
237-
curTarget.addModuleAlias(for: nameToBeAliased, as: aliasInChild)
274+
} else if curTarget.name != nameToBeAliased {
275+
// Targets to be renamed already have aliases, so
276+
// process their depending targets here
277+
if curTarget.moduleAliases?[nameToBeAliased] == nil {
278+
// Remove any pre-chained aliases, i.e. value
279+
// of a module alias defined upstream that's used
280+
// as a key downstream
281+
let preChainedAliases = curTarget.moduleAliases?.filter{$0.value == aliasInCurChild} ?? [:]
282+
for preChainedAlias in preChainedAliases {
283+
if preChainedAlias.key != nameToBeAliased {
284+
curTarget.removeModuleAlias(for: preChainedAlias.key)
285+
}
286+
}
287+
curTarget.addModuleAlias(for: nameToBeAliased, as: aliasInCurChild)
288+
}
238289
}
239290
}
240291
}

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ private func createResolvedPackages(
246246

247247
// Resolve module aliases, if specified, for targets and their dependencies
248248
// across packages. Aliasing will result in target renaming.
249-
let moduleAliasingUsed = try resolveModuleAliases(packageBuilders: packageBuilders)
249+
let moduleAliasingUsed = try resolveModuleAliases(packageBuilders: packageBuilders, observabilityScope: observabilityScope)
250250

251251
// Scan and validate the dependencies
252252
for packageBuilder in packageBuilders {
@@ -641,8 +641,8 @@ private func computePlatforms(
641641
}
642642

643643
// Track and override module aliases specified for targets in a package graph
644-
private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder]) throws -> Bool {
645-
644+
private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder],
645+
observabilityScope: ObservabilityScope) throws -> Bool {
646646
// If there are no module aliases specified, return early
647647
let hasAliases = packageBuilders.contains { $0.package.targets.contains {
648648
$0.dependencies.contains { dep in
@@ -670,7 +670,7 @@ private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder]) thr
670670
}
671671

672672
// Override module aliases upstream if needed
673-
aliasTracker.propagateAliases()
673+
aliasTracker.propagateAliases(observabilityScope: observabilityScope)
674674

675675
// Validate sources (Swift files only) for modules being aliased.
676676
// Needs to be done after `propagateAliases` since aliases defined

0 commit comments

Comments
 (0)