Skip to content

Commit 53b33c4

Browse files
authored
Reorganize files for module aliasing (#5529)
Resolves rdar://93611579
1 parent 3836401 commit 53b33c4

File tree

7 files changed

+2614
-2592
lines changed

7 files changed

+2614
-2592
lines changed

Sources/PackageGraph/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_library(PackageGraph
1313
DependencyResolver.swift
1414
Diagnostics.swift
1515
GraphLoadingNode.swift
16+
ModuleAliasTracker.swift
1617
PackageContainer.swift
1718
PackageGraph.swift
1819
PackageGraph+Loading.swift
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
import PackageModel
2+
3+
// This class helps track module aliases in a package graph and override
4+
// upstream alises if needed
5+
class ModuleAliasTracker {
6+
var aliasMap = [PackageIdentity: [String: [ModuleAliasModel]]]()
7+
var idToProductToAllTargets = [PackageIdentity: [String: [Target]]]()
8+
var productToDirectTargets = [String: [Target]]()
9+
var productToAllTargets = [String: [Target]]()
10+
var childToParentProducts = [String: [String]]()
11+
var parentToChildProducts = [String: [String]]()
12+
var cachedChildToParentProducts = [String: [String]]()
13+
var parentToChildIDs = [PackageIdentity: [PackageIdentity]]()
14+
var childToParentID = [PackageIdentity: PackageIdentity]()
15+
16+
init() {}
17+
func addTargetAliases(targets: [Target], package: PackageIdentity) throws {
18+
let targetDependencies = targets.map{$0.dependencies}.flatMap{$0}
19+
for dep in targetDependencies {
20+
if case let .product(productRef, _) = dep,
21+
let productPkg = productRef.package {
22+
let productPkgID = PackageIdentity.plain(productPkg)
23+
// Track dependency package ID chain
24+
addPackageIDChain(parent: package, child: productPkgID)
25+
if let aliasList = productRef.moduleAliases {
26+
// Track aliases for this product
27+
try addAliases(aliasList,
28+
product: productRef.name,
29+
originPackage: productPkgID,
30+
consumingPackage: package)
31+
}
32+
}
33+
}
34+
}
35+
36+
func addAliases(_ aliases: [String: String],
37+
product: String,
38+
originPackage: PackageIdentity,
39+
consumingPackage: PackageIdentity) throws {
40+
if let aliasDict = aliasMap[originPackage] {
41+
let existingAliases = aliasDict.values.flatMap{$0}.filter { aliases.keys.contains($0.name) }
42+
for existingAlias in existingAliases {
43+
if let newAlias = aliases[existingAlias.name], newAlias != existingAlias.alias {
44+
// Error if there are multiple different aliases specified for
45+
// targets in this product
46+
throw PackageGraphError.multipleModuleAliases(target: existingAlias.name, product: product, package: originPackage.description, aliases: existingAliases.map{$0.alias} + [newAlias])
47+
}
48+
}
49+
}
50+
51+
for (originalName, newName) in aliases {
52+
let model = ModuleAliasModel(name: originalName, alias: newName, originPackage: originPackage, consumingPackage: consumingPackage)
53+
aliasMap[originPackage, default: [:]][product, default: []].append(model)
54+
}
55+
}
56+
57+
func addPackageIDChain(parent: PackageIdentity,
58+
child: PackageIdentity) {
59+
if parentToChildIDs[parent]?.contains(child) ?? false {
60+
// Already added
61+
} else {
62+
parentToChildIDs[parent, default: []].append(child)
63+
// Used to track the top-most level package
64+
childToParentID[child] = parent
65+
}
66+
}
67+
68+
// This func should be called once per product
69+
func trackTargetsPerProduct(product: Product,
70+
package: PackageIdentity) {
71+
let targetDeps = product.targets.map{$0.dependencies}.flatMap{$0}
72+
var allTargetDeps = product.targets.map{$0.dependentTargets().map{$0.dependencies}}.flatMap{$0}.flatMap{$0}
73+
allTargetDeps.append(contentsOf: targetDeps)
74+
for dep in allTargetDeps {
75+
if case let .product(depRef, _) = dep {
76+
childToParentProducts[depRef.name, default: []].append(product.name)
77+
parentToChildProducts[product.name, default: []].append(depRef.name)
78+
}
79+
}
80+
81+
var allTargetsInProduct = targetDeps.compactMap{$0.target}
82+
allTargetsInProduct.append(contentsOf: product.targets)
83+
idToProductToAllTargets[package, default: [:]][product.name] = allTargetsInProduct
84+
productToDirectTargets[product.name] = product.targets
85+
productToAllTargets[product.name] = allTargetsInProduct
86+
}
87+
88+
func validateAndApplyAliases(product: String,
89+
package: PackageIdentity) throws {
90+
guard let targets = idToProductToAllTargets[package]?[product] else { return }
91+
let targetsWithAliases = targets.filter{ $0.moduleAliases != nil }
92+
for target in targetsWithAliases {
93+
if target.sources.containsNonSwiftFiles {
94+
throw PackageGraphError.invalidSourcesForModuleAliasing(target: target.name, product: product, package: package.description)
95+
}
96+
target.applyAlias()
97+
}
98+
}
99+
100+
func propagateAliases() {
101+
// First get the root package ID
102+
var pkgID = childToParentID.first?.key
103+
var rootPkg = pkgID
104+
while pkgID != nil {
105+
rootPkg = pkgID
106+
// pkgID is not nil here so can be force unwrapped
107+
pkgID = childToParentID[pkgID!]
108+
}
109+
guard let rootPkg = rootPkg else { return }
110+
// Propagate and override upstream aliases if needed
111+
var aliasBuffer = [String: ModuleAliasModel]()
112+
propagate(package: rootPkg, aliasBuffer: &aliasBuffer)
113+
// Now merge overriden upstream aliases and add them to
114+
// downstream targets
115+
if let productToAllTargets = idToProductToAllTargets[rootPkg] {
116+
for productName in productToAllTargets.keys {
117+
mergeAliases(product: productName)
118+
}
119+
}
120+
}
121+
122+
// Traverse upstream and override aliases for the same targets if needed
123+
func propagate(package: PackageIdentity, aliasBuffer: inout [String: ModuleAliasModel]) {
124+
if let curProductToTargetAliases = aliasMap[package] {
125+
let curAliasModels = curProductToTargetAliases.map {$0.value}.filter{!$0.isEmpty}.flatMap{$0}
126+
for aliasModel in curAliasModels {
127+
// A buffer is used to track the most downstream aliases
128+
// (hence the nil check here) to allow overriding upstream
129+
// aliases for targets; if the downstream aliases are applied
130+
// to upstream targets, then they get removed
131+
if aliasBuffer[aliasModel.name] == nil {
132+
// Add a target name as a key. The buffer only tracks
133+
// a target that needs to be renamed, not the depending
134+
// targets which might have multiple target dependencies
135+
// with their aliases, so add a single alias model as value.
136+
aliasBuffer[aliasModel.name] = aliasModel
137+
}
138+
}
139+
}
140+
if let curProductToTargets = idToProductToAllTargets[package] {
141+
// Check if targets for the products in this package have
142+
// aliases tracked by the buffer
143+
let curProductToTargetsToAlias = curProductToTargets.filter { $0.value.contains { aliasBuffer[$0.name] != nil } }
144+
if !curProductToTargetsToAlias.isEmpty {
145+
var usedKeys = Set<String>()
146+
for (curProductName, targetsForCurProduct) in curProductToTargets {
147+
if let targetListToAlias = curProductToTargetsToAlias[curProductName] {
148+
for targetToAlias in targetListToAlias {
149+
if let aliasModel = aliasBuffer[targetToAlias.name] {
150+
var didAlias = false
151+
for curTarget in targetsForCurProduct {
152+
// Check if curTarget is relevant for aliasing
153+
let canAlias = curTarget.name == aliasModel.name || curTarget.dependencies.contains { $0.name == aliasModel.name }
154+
if canAlias {
155+
curTarget.addModuleAlias(for: aliasModel.name, as: aliasModel.alias)
156+
didAlias = true
157+
}
158+
}
159+
if didAlias {
160+
usedKeys.insert(targetToAlias.name)
161+
}
162+
}
163+
}
164+
}
165+
}
166+
for used in usedKeys {
167+
// Remove an entry for a used alias
168+
aliasBuffer.removeValue(forKey: used)
169+
}
170+
}
171+
}
172+
guard let children = parentToChildIDs[package] else { return }
173+
for childID in children {
174+
propagate(package: childID, aliasBuffer: &aliasBuffer)
175+
}
176+
}
177+
178+
// Merge overriden upstream aliases and add them to downstream targets
179+
func mergeAliases(product: String) {
180+
guard let childProducts = parentToChildProducts[product] else { return }
181+
for child in childProducts {
182+
mergeAliases(product: child)
183+
// Filter out targets in the current product with names that are
184+
// aliased with different values in the child products since they
185+
// should either not be aliased or their existing aliases if any
186+
// should not be overridden.
187+
let allTargetNamesInCurProduct = productToAllTargets[product]?.compactMap{$0.name} ?? []
188+
let childTargetsAliases = productToDirectTargets[child]?.compactMap{$0.moduleAliases}.flatMap{$0}.filter{ !allTargetNamesInCurProduct.contains($0.key) }
189+
190+
if let childTargetsAliases = childTargetsAliases,
191+
let directTargets = productToDirectTargets[product] {
192+
// Keep track of all targets in this product that directly
193+
// or indirectly depend on the child product
194+
let directRelevantTargets = directTargets.filter {$0.dependsOn(product: child)}
195+
var relevantTargets = directTargets.map{$0.dependentTargets()}.flatMap{$0}.filter {$0.dependsOn(product: child)}
196+
relevantTargets.append(contentsOf: directTargets)
197+
relevantTargets.append(contentsOf: directRelevantTargets)
198+
let relevantTargetSet = Set(relevantTargets)
199+
200+
// Used to compare with aliases defined in other child products
201+
// and detect a conflict if any.
202+
let allTargetsInOtherChildProducts = childProducts.filter{$0 != child }.compactMap{productToAllTargets[$0]}.flatMap{$0}
203+
let allTargetNamesInChildProduct = productToAllTargets[child]?.map{$0.name} ?? []
204+
for curTarget in relevantTargetSet {
205+
for (nameToBeAliased, aliasInChild) in childTargetsAliases {
206+
// If there are targets in other child products that
207+
// have the same name that's being aliased here, but
208+
// targets in this child product don't, we need to use
209+
// alias values of those targets as they take a precedence
210+
let otherAliasesInChildProducts = allTargetsInOtherChildProducts.filter{$0.name == nameToBeAliased}.compactMap{$0.moduleAliases}.flatMap{$0}.filter{$0.key == nameToBeAliased}
211+
if !otherAliasesInChildProducts.isEmpty,
212+
!allTargetNamesInChildProduct.contains(curTarget.name) {
213+
for (aliasKey, aliasValue) in otherAliasesInChildProducts {
214+
// Reset the old alias value with this aliasValue
215+
if curTarget.moduleAliases?[aliasKey] != aliasValue {
216+
curTarget.addModuleAlias(for: aliasKey, as: aliasValue)
217+
}
218+
}
219+
} else {
220+
// If there are no aliases or conflicting aliases
221+
// for the same key defined in other child products,
222+
// those aliases should be removed from this target.
223+
let hasConflict = allTargetsInOtherChildProducts.contains{ otherTarget in
224+
if let otherAlias = otherTarget.moduleAliases?[nameToBeAliased] {
225+
return otherAlias != aliasInChild
226+
} else {
227+
return otherTarget.name == nameToBeAliased
228+
}
229+
}
230+
if hasConflict {
231+
// If there are aliases, remove as aliasing should
232+
// not be applied
233+
curTarget.removeModuleAlias(for: nameToBeAliased)
234+
} else if curTarget.moduleAliases?[nameToBeAliased] == nil {
235+
// Otherwise add the alias if none exists
236+
curTarget.addModuleAlias(for: nameToBeAliased, as: aliasInChild)
237+
}
238+
}
239+
}
240+
}
241+
}
242+
}
243+
}
244+
}
245+
246+
// Used to keep track of module alias info for each package
247+
class ModuleAliasModel {
248+
let name: String
249+
var alias: String
250+
let originPackage: PackageIdentity
251+
let consumingPackage: PackageIdentity
252+
253+
init(name: String, alias: String, originPackage: PackageIdentity, consumingPackage: PackageIdentity) {
254+
self.name = name
255+
self.alias = alias
256+
self.originPackage = originPackage
257+
self.consumingPackage = consumingPackage
258+
}
259+
}
260+
261+
extension Target {
262+
func dependsOn(product: String) -> Bool {
263+
return dependencies.contains { dep in
264+
if case let .product(prodRef, _) = dep {
265+
return prodRef.name == product
266+
}
267+
return false
268+
}
269+
}
270+
271+
func dependentTargets() -> [Target] {
272+
return dependencies.compactMap{$0.target}
273+
}
274+
}
275+

0 commit comments

Comments
 (0)