Skip to content

Commit 4fd61ba

Browse files
authored
Update alias propagation / handle edge cases (#4331)
Update module alias tracker Move module aliases setting per target into alias tracker Improve perf Handle edge cases Add more tests Resolves rdar://90337780
1 parent eee97d7 commit 4fd61ba

File tree

3 files changed

+434
-131
lines changed

3 files changed

+434
-131
lines changed

Sources/PackageGraph/PackageGraph+Loading.swift

Lines changed: 144 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -199,32 +199,6 @@ private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], obse
199199
}
200200
}
201201

202-
extension Package {
203-
// Add module aliases specified for applicable targets
204-
fileprivate func setModuleAliasesForTargets(with moduleAliasMap: [String: [ModuleAliasModel]]) {
205-
// Set module aliases for each target's dependencies
206-
for target in self.targets {
207-
let aliasesForTarget = moduleAliasMap.filter {$0.key == target.name}.values.flatMap{$0}
208-
for entry in aliasesForTarget {
209-
if entry.name != target.name {
210-
target.addModuleAlias(for: entry.name, as: entry.alias)
211-
}
212-
}
213-
}
214-
215-
// This loop should run after the loop above as it may rename the target
216-
// as an alias if specified
217-
for target in self.targets {
218-
let aliasesForTarget = moduleAliasMap.filter {$0.key == target.name}.values.flatMap{$0}
219-
for entry in aliasesForTarget {
220-
if entry.name == target.name {
221-
target.addModuleAlias(for: entry.name, as: entry.alias)
222-
}
223-
}
224-
}
225-
}
226-
}
227-
228202
fileprivate extension ResolvedProduct {
229203
/// Returns true if and only if the product represents a command plugin target.
230204
var isCommandPlugin: Bool {
@@ -270,9 +244,9 @@ private func createResolvedPackages(
270244
return ($0.package.identity, $0)
271245
}
272246

273-
// Gather and resolve module aliases specified for targets in all dependent packages
274-
let packageAliases = try resolveModuleAliases(packageBuilders: packageBuilders,
275-
observabilityScope: observabilityScope)
247+
// Resolve module aliases, if specified, for targets and their dependencies
248+
// across packages. Aliasing will result in target renaming.
249+
try resolveModuleAliases(packageBuilders: packageBuilders)
276250

277251
// Scan and validate the dependencies
278252
for packageBuilder in packageBuilders {
@@ -283,10 +257,6 @@ private func createResolvedPackages(
283257
metadata: package.diagnosticsMetadata
284258
)
285259

286-
if let aliasMap = packageAliases?[package.identity] {
287-
package.setModuleAliasesForTargets(with: aliasMap)
288-
}
289-
290260
var dependencies = OrderedCollections.OrderedDictionary<PackageIdentity, ResolvedPackageBuilder>()
291261
var dependenciesByNameForTargetDependencyResolution = [String: ResolvedPackageBuilder]()
292262

@@ -620,8 +590,7 @@ private func computePlatforms(
620590
}
621591

622592
// Track and override module aliases specified for targets in a package graph
623-
private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder],
624-
observabilityScope: ObservabilityScope) throws -> [PackageIdentity: [String: [ModuleAliasModel]]]? {
593+
private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder]) throws {
625594

626595
// If there are no module aliases specified, return early
627596
let hasAliases = packageBuilders.contains { $0.package.targets.contains {
@@ -634,40 +603,18 @@ private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder],
634603
}
635604
}
636605

637-
guard hasAliases else { return nil }
606+
guard hasAliases else { return }
638607
let aliasTracker = ModuleAliasTracker()
639608
for packageBuilder in packageBuilders {
640-
for target in packageBuilder.package.targets {
641-
for dep in target.dependencies {
642-
if case let .product(prodRef, _) = dep,
643-
let prodPkg = prodRef.package {
644-
let prodPkgID = PackageIdentity.plain(prodPkg)
645-
// Track package ID dependency chain
646-
aliasTracker.addPackageIDChain(parent: packageBuilder.package.identity, child: prodPkgID)
647-
648-
if let aliasList = prodRef.moduleAliases {
649-
for (depName, depAlias) in aliasList {
650-
// Track aliases for this product
651-
try aliasTracker.addAlias(depAlias,
652-
target: depName,
653-
product: prodRef.name,
654-
originPackage: PackageIdentity.plain(prodPkg),
655-
consumingPackage: packageBuilder.package.identity)
656-
}
657-
}
658-
}
659-
}
660-
}
609+
try aliasTracker.addTargetAliases(targets: packageBuilder.package.targets,
610+
package: packageBuilder.package.identity)
661611
}
662612

663613
// Track targets that need module aliases for each package
664614
for packageBuilder in packageBuilders {
665615
for product in packageBuilder.package.products {
666-
var allTargets = product.targets.map{$0.dependencies}.flatMap{$0}.compactMap{$0.target}
667-
allTargets.append(contentsOf: product.targets)
668-
aliasTracker.addAliasesForTargets(allTargets,
669-
product: product.name,
670-
package: packageBuilder.package.identity)
616+
aliasTracker.trackTargetsPerProduct(product: product,
617+
package: packageBuilder.package.identity)
671618
}
672619
}
673620

@@ -679,46 +626,67 @@ private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder],
679626
// upstream can be overriden.
680627
for packageBuilder in packageBuilders {
681628
for product in packageBuilder.package.products {
682-
var allTargets = product.targets.map{$0.dependencies}.flatMap{$0}.compactMap{$0.target}
683-
allTargets.append(contentsOf: product.targets)
684-
try aliasTracker.validateSources(allTargets,
685-
product: product.name,
686-
package: packageBuilder.package.identity)
629+
try aliasTracker.validateAndApplyAliases(product: product.name,
630+
package: packageBuilder.package.identity)
687631
}
688632
}
689-
690-
return aliasTracker.idTargetToAliases
691633
}
692634

693635
// This class helps track module aliases in a package graph and override
694636
// upstream alises if needed
695637
private class ModuleAliasTracker {
696638
var aliasMap = [PackageIdentity: [String: [ModuleAliasModel]]]()
697-
var idTargetToAliases = [PackageIdentity: [String: [ModuleAliasModel]]]()
639+
var idToProductToAllTargets = [PackageIdentity: [String: [Target]]]()
640+
var productToDirectTargets = [String: [Target]]()
641+
var productToAllTargets = [String: [Target]]()
642+
var childToParentProducts = [String: [String]]()
643+
var cachedChildToParentProducts = [String: [String]]()
698644
var parentToChildIDs = [PackageIdentity: [PackageIdentity]]()
699645
var childToParentID = [PackageIdentity: PackageIdentity]()
700646

701647
init() {}
648+
func addTargetAliases(targets: [Target], package: PackageIdentity) throws {
649+
let targetDependencies = targets.map{$0.dependencies}.flatMap{$0}
650+
for dep in targetDependencies {
651+
if case let .product(productRef, _) = dep,
652+
let productPkg = productRef.package {
653+
let productPkgID = PackageIdentity.plain(productPkg)
654+
// Track dependency package ID chain
655+
addPackageIDChain(parent: package, child: productPkgID)
656+
if let aliasList = productRef.moduleAliases {
657+
// Track aliases for this product
658+
try addAliases(aliasList,
659+
product: productRef.name,
660+
originPackage: productPkgID,
661+
consumingPackage: package)
662+
}
663+
}
664+
}
665+
}
702666

703-
func addAlias(_ alias: String,
704-
target: String,
705-
product: String,
706-
originPackage: PackageIdentity,
707-
consumingPackage: PackageIdentity) throws {
667+
func addAliases(_ aliases: [String: String],
668+
product: String,
669+
originPackage: PackageIdentity,
670+
consumingPackage: PackageIdentity) throws {
708671
if let aliasDict = aliasMap[originPackage] {
709-
let models = aliasDict.values.flatMap{$0}.filter { $0.name == target }
710-
if !models.isEmpty {
711-
// Error if there are multiple aliases specified for this product dependency
712-
throw PackageGraphError.multipleModuleAliases(target: target, product: product, package: originPackage.description, aliases: models.map{$0.alias} + [alias])
672+
let existingAliases = aliasDict.values.flatMap{$0}.filter { aliases.keys.contains($0.name) }
673+
for existingAlias in existingAliases {
674+
if let newAlias = aliases[existingAlias.name], newAlias != existingAlias.alias {
675+
// Error if there are multiple different aliases specified for
676+
// targets in this product
677+
throw PackageGraphError.multipleModuleAliases(target: existingAlias.name, product: product, package: originPackage.description, aliases: existingAliases.map{$0.alias} + [newAlias])
678+
}
713679
}
714680
}
715681

716-
let model = ModuleAliasModel(name: target, alias: alias, originPackage: originPackage, consumingPackage: consumingPackage)
717-
aliasMap[originPackage, default: [:]][product, default: []].append(model)
682+
for (originalName, newName) in aliases {
683+
let model = ModuleAliasModel(name: originalName, alias: newName, originPackage: originPackage, consumingPackage: consumingPackage)
684+
aliasMap[originPackage, default: [:]][product, default: []].append(model)
685+
}
718686
}
719687

720688
func addPackageIDChain(parent: PackageIdentity,
721-
child: PackageIdentity) {
689+
child: PackageIdentity) {
722690
if parentToChildIDs[parent]?.contains(child) ?? false {
723691
// Already added
724692
} else {
@@ -728,30 +696,31 @@ private class ModuleAliasTracker {
728696
}
729697
}
730698

731-
func addAliasesForTargets(_ targets: [Target],
732-
product: String,
733-
package: PackageIdentity) {
734-
let aliases = aliasMap[package]?[product]
735-
for target in targets {
736-
if idTargetToAliases[package]?[target.name] == nil {
737-
idTargetToAliases[package, default: [:]][target.name] = []
738-
}
739-
740-
if let aliases = aliases {
741-
idTargetToAliases[package]?[target.name]?.append(contentsOf: aliases)
699+
// This func should be called once per product
700+
func trackTargetsPerProduct(product: Product,
701+
package: PackageIdentity) {
702+
let productTargetDeps = product.targets.map{$0.dependencies}.flatMap{$0}
703+
for dep in productTargetDeps {
704+
if case let .product(depRef, _) = dep {
705+
childToParentProducts[depRef.name, default: []].append(product.name)
742706
}
743707
}
708+
var allTargets = productTargetDeps.compactMap{$0.target}
709+
allTargets.append(contentsOf: product.targets)
710+
idToProductToAllTargets[package, default: [:]][product.name] = allTargets
711+
productToDirectTargets[product.name] = product.targets
712+
productToAllTargets[product.name] = allTargets
744713
}
745714

746-
func validateSources(_ targets: [Target],
747-
product: String,
748-
package: PackageIdentity) throws {
749-
for target in targets {
750-
if let aliases = idTargetToAliases[package]?[target.name], !aliases.isEmpty {
751-
if target.sources.containsNonSwiftFiles {
752-
throw PackageGraphError.invalidSourcesForModuleAliasing(target: target.name, product: product, package: package.description)
753-
}
715+
func validateAndApplyAliases(product: String,
716+
package: PackageIdentity) throws {
717+
guard let targets = idToProductToAllTargets[package]?[product] else { return }
718+
let targetsWithAliases = targets.filter{ $0.moduleAliases != nil }
719+
for target in targetsWithAliases {
720+
if target.sources.containsNonSwiftFiles {
721+
throw PackageGraphError.invalidSourcesForModuleAliasing(target: target.name, product: product, package: package.description)
754722
}
723+
target.applyAlias()
755724
}
756725
}
757726

@@ -764,34 +733,90 @@ private class ModuleAliasTracker {
764733
// pkgID is not nil here so can be force unwrapped
765734
pkgID = childToParentID[pkgID!]
766735
}
767-
768736
guard let rootPkg = rootPkg else { return }
769-
propagate(from: rootPkg)
737+
var aliasBuffer = [String: ModuleAliasModel]()
738+
var targetBuffer = [Target]()
739+
propagate(package: rootPkg, parent: nil, targetBuffer: &targetBuffer, aliasBuffer: &aliasBuffer)
770740
}
771741

772-
func propagate(from cur: PackageIdentity) {
773-
guard let children = parentToChildIDs[cur] else { return }
774-
for child in children {
775-
if let parentMap = idTargetToAliases[cur],
776-
let childMap = idTargetToAliases[child] {
777-
for (_, parentAliases) in parentMap {
778-
for parentModel in parentAliases {
779-
for (childTarget, childAliases) in childMap {
780-
if !parentMap.keys.contains(childTarget),
781-
childTarget == parentModel.name {
782-
if childAliases.isEmpty {
783-
idTargetToAliases[child]?[childTarget]?.append(parentModel)
784-
} else {
785-
for childModel in childAliases {
786-
childModel.alias = parentModel.alias
742+
func propagate(package: PackageIdentity, parent: PackageIdentity?, targetBuffer: inout [Target], aliasBuffer: inout [String: ModuleAliasModel]) {
743+
if let curProductToTargetAliases = aliasMap[package] {
744+
let curAliasModels = curProductToTargetAliases.map {$0.value}.filter{!$0.isEmpty}.flatMap{$0}
745+
for aliasModel in curAliasModels {
746+
// A buffer is used to track the most downstream aliases
747+
// (hence the nil check here) to allow overriding upstream
748+
// aliases for targets; if the downstream aliases are applied
749+
// to upstream targets, then they get removed
750+
if aliasBuffer[aliasModel.name] == nil {
751+
// Add a target name as a key. The buffer only tracks
752+
// a target that needs to be renamed, not the depending
753+
// targets which might have multiple target dependencies
754+
// with their aliases, so add a single alias model as value.
755+
aliasBuffer[aliasModel.name] = aliasModel
756+
}
757+
}
758+
}
759+
if let curProductToTargets = idToProductToAllTargets[package] {
760+
// Check if targets for the products in this package have
761+
// aliases tracked by the buffer
762+
let curProductToTargetsWithAliases = curProductToTargets.filter { $0.value.contains { aliasBuffer[$0.name] != nil } }
763+
if !curProductToTargetsWithAliases.isEmpty {
764+
var usedKeys = [String]()
765+
for (curProductName, targetsForCurProduct) in curProductToTargets {
766+
if let commonAliasesForTargets = curProductToTargetsWithAliases[curProductName],
767+
let nameKey = commonAliasesForTargets.first?.name,
768+
let aliasModel = aliasBuffer[nameKey] {
769+
for curTarget in targetsForCurProduct {
770+
curTarget.addModuleAlias(for: aliasModel.name, as: aliasModel.alias)
771+
}
772+
// Once adding aliases for targets in this product, need to
773+
// add them to targets in the parent product since they can
774+
// import the now aliased targets
775+
var parentProducts = [String]()
776+
gatherParentProducts(curProductName, result: &parentProducts)
777+
for parentProduct in parentProducts {
778+
let hasConflictWithAlias = productToAllTargets[parentProduct]?.contains { $0.name == aliasModel.name } ?? false
779+
// if conflicting, parent target will need to
780+
// directly import the child target that's renamed
781+
if !hasConflictWithAlias {
782+
let targetsToAlias = productToDirectTargets[parentProduct]?.filter { targetArg in
783+
if let aliases = targetArg.moduleAliases {
784+
// if already aliased, should not override
785+
return aliases[aliasModel.name] == nil
787786
}
787+
return true
788+
} ?? []
789+
for parentTarget in targetsToAlias {
790+
parentTarget.addModuleAlias(for: aliasModel.name, as: aliasModel.alias)
788791
}
789792
}
790793
}
794+
usedKeys.append(nameKey)
791795
}
792796
}
797+
for used in usedKeys {
798+
// Remove an entry for a used alias
799+
aliasBuffer.removeValue(forKey: used)
800+
}
801+
}
802+
}
803+
guard let children = parentToChildIDs[package] else { return }
804+
for childID in children {
805+
propagate(package: childID, parent: package, targetBuffer: &targetBuffer, aliasBuffer: &aliasBuffer)
806+
}
807+
}
808+
809+
func gatherParentProducts(_ productName: String, result: inout [String]) {
810+
if let cached = cachedChildToParentProducts[productName] {
811+
result.append(contentsOf: cached)
812+
return
813+
}
814+
if let parents = childToParentProducts[productName] {
815+
result.append(contentsOf: parents)
816+
cachedChildToParentProducts[productName, default: []].append(contentsOf: parents)
817+
for parentProd in parents {
818+
gatherParentProducts(parentProd, result: &result)
793819
}
794-
propagate(from: child)
795820
}
796821
}
797822
}

Sources/PackageModel/Target.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,17 @@ public class Target: PolymorphicCodableProtocol {
137137
} else {
138138
moduleAliases?[name] = alias
139139
}
140+
}
140141

141-
// If the argument name is same as this target's name, this
142-
// target should be renamed as the argument alias.
143-
if name == self.name {
142+
@discardableResult
143+
public func applyAlias() -> Bool {
144+
// If there's an alias for this target, rename
145+
if let alias = moduleAliases?[name] {
144146
self.name = alias
145147
self.c99name = alias.spm_mangledToC99ExtendedIdentifier()
148+
return true
146149
}
150+
return false
147151
}
148152

149153
/// The dependencies of this target.

0 commit comments

Comments
 (0)