@@ -384,69 +384,8 @@ private func createResolvedPackages(
384
384
}
385
385
}
386
386
387
- // Find duplicate products in the package graph.
388
- let productList = packageBuilders. flatMap ( { $0. products } ) . map ( { $0. product } )
389
-
390
- if moduleAliasingUsed {
391
- // FIXME: If moduleAliasingUsed, we want to allow duplicate product names
392
- // from different packages as often times the product name of a package is
393
- // same as its target name which might have a conflict with the name of the
394
- // product itself or its target from another package.
395
- // The following is a workaround; eventually we want to allow duplicate product
396
- // names even when module aliasing is not used, which is a cleaner solution.
397
- // Ref rdar://94744134.
398
-
399
- // We first divide the products by type which determines whether to use
400
- // the product ID (unique, fully qualified name) or name to look up duplicates.
401
-
402
- // There are no shared dirs/files created for automatic library products, so look
403
- // up duplicates with the ID property for those products.
404
- let autoLibProducts = productList
405
- . filter { $0. isDefaultLibrary }
406
- . spm_findDuplicateElements ( by: \. ID)
407
- . map ( { $0 [ 0 ] } )
408
-
409
- // Building other products, i.e. static libs, dylibs, executables, result in
410
- // shared dirs/files, e.g. Foo.product (dir), libFoo.dylib, etc., so we want
411
- // to keep the original product names for those. Thus, use the name property
412
- // to look up duplicates.
413
- let otherProducts = productList
414
- . filter { !$0. isDefaultLibrary }
415
- . spm_findDuplicateElements ( by: \. name)
416
- . map ( { $0 [ 0 ] } )
417
-
418
- let allProducts = autoLibProducts + otherProducts
419
- // Emit diagnostics for duplicate products.
420
- for dupProduct in allProducts {
421
- let packages = packageBuilders
422
- . filter ( { $0. products. contains ( where: { $0. product. isDefaultLibrary ? $0. product. ID == dupProduct. ID : $0. product. name == dupProduct. name } ) } )
423
- . map { $0. package . identity. description }
424
- . sorted ( )
425
- observabilityScope. emit ( PackageGraphError . duplicateProduct ( product: dupProduct. name, packages: packages) )
426
- }
427
- // Remove the duplicate products from the builders.
428
- let autoLibProductIDs = autoLibProducts. map { $0. ID }
429
- let otherProductNames = otherProducts. map { $0. name }
430
- for packageBuilder in packageBuilders {
431
- packageBuilder. products = packageBuilder. products. filter { $0. product. isDefaultLibrary ? !autoLibProductIDs. contains ( $0. product. ID) : !otherProductNames. contains ( $0. product. name) }
432
- }
433
- } else {
434
- let duplicateProducts = productList
435
- . spm_findDuplicateElements ( by: \. name)
436
- . map ( { $0 [ 0 ] } )
437
- // Emit diagnostics for duplicate products.
438
- for dupProduct in duplicateProducts {
439
- let packages = packageBuilders
440
- . filter ( { $0. products. contains ( where: { $0. product. name == dupProduct. name } ) } )
441
- . map { $0. package . identity. description }
442
- . sorted ( )
443
- observabilityScope. emit ( PackageGraphError . duplicateProduct ( product: dupProduct. name, packages: packages) )
444
- }
445
- // Remove the duplicate products from the builders.
446
- for packageBuilder in packageBuilders {
447
- packageBuilder. products = packageBuilder. products. filter { !duplicateProducts. map { $0. name} . contains ( $0. product. name) }
448
- }
449
- }
387
+ let dupProductsChecker = DuplicateProductsChecker ( packageBuilders: packageBuilders)
388
+ try dupProductsChecker. run ( lookupByProductIDs: moduleAliasingUsed, observabilityScope: observabilityScope)
450
389
451
390
// The set of all target names.
452
391
var allTargetNames = Set < String > ( )
@@ -473,14 +412,19 @@ private func createResolvedPackages(
473
412
return false
474
413
} )
475
414
415
+ let lookupByProductIDs = packageBuilder. package . manifest. disambiguateByProductIDs || moduleAliasingUsed
416
+
476
417
// Get all the products from dependencies of this package.
477
418
let productDependencies = packageBuilder. dependencies
478
419
. flatMap ( { ( dependency: ResolvedPackageBuilder ) -> [ ResolvedProductBuilder ] in
479
420
// Filter out synthesized products such as tests and implicit executables.
480
- let explicit = Set ( dependency. package . manifest. products. lazy. map ( { $0. name } ) )
481
- return dependency. products. filter ( { explicit. contains ( $0. product. name) } )
421
+ // Check if a dependency product is explicitly declared as a product in its package manifest
422
+ let manifestProducts = dependency. package . manifest. products. lazy. map { $0. name }
423
+ let explicitProducts = dependency. package . products. filter { manifestProducts. contains ( $0. name) }
424
+ let explicitIdsOrNames = Set ( explicitProducts. lazy. map ( { lookupByProductIDs ? $0. identity : $0. name } ) )
425
+ return dependency. products. filter ( { lookupByProductIDs ? explicitIdsOrNames. contains ( $0. product. identity) : explicitIdsOrNames. contains ( $0. product. name) } )
482
426
} )
483
- let productDependencyMap = moduleAliasingUsed ? productDependencies. spm_createDictionary ( { ( $0. product. ID , $0) } ) : productDependencies. spm_createDictionary ( { ( $0. product. name, $0) } )
427
+ let productDependencyMap = lookupByProductIDs ? productDependencies. spm_createDictionary ( { ( $0. product. identity , $0) } ) : productDependencies. spm_createDictionary ( { ( $0. product. name, $0) } )
484
428
485
429
// Establish dependencies in each target.
486
430
for targetBuilder in packageBuilder. targets {
@@ -494,7 +438,7 @@ private func createResolvedPackages(
494
438
for case . product( let productRef, let conditions) in targetBuilder. target. dependencies {
495
439
// Find the product in this package's dependency products.
496
440
// Look it up by ID if module aliasing is used, otherwise by name.
497
- let product = moduleAliasingUsed ? productDependencyMap [ productRef. ID ] : productDependencyMap [ productRef. name]
441
+ let product = lookupByProductIDs ? productDependencyMap [ productRef. identity ] : productDependencyMap [ productRef. name]
498
442
guard let product = product else {
499
443
// Only emit a diagnostic if there are no other diagnostics.
500
444
// This avoids flooding the diagnostics with product not
@@ -503,7 +447,7 @@ private func createResolvedPackages(
503
447
if !observabilityScope. errorsReportedInAnyScope {
504
448
// Emit error if a product (not target) declared in the package is also a productRef (dependency)
505
449
let declProductsAsDependency = package . products. filter { product in
506
- product. name == productRef. name
450
+ lookupByProductIDs ? product . identity == productRef . identity : product. name == productRef. name
507
451
} . map { $0. targets} . flatMap { $0} . filter { t in
508
452
t. name != productRef. name
509
453
}
@@ -570,6 +514,56 @@ fileprivate extension Product {
570
514
}
571
515
}
572
516
517
+ private class DuplicateProductsChecker {
518
+ var packageIDToBuilder = [ String: ResolvedPackageBuilder] ( )
519
+ var checkedPkgIDs = [ String] ( )
520
+
521
+ init ( packageBuilders: [ ResolvedPackageBuilder ] ) {
522
+ for packageBuilder in packageBuilders {
523
+ let pkgID = packageBuilder. package . identity. description. lowercased ( )
524
+ packageIDToBuilder [ pkgID] = packageBuilder
525
+ }
526
+ }
527
+
528
+ func run( lookupByProductIDs: Bool = false , observabilityScope: ObservabilityScope ) throws {
529
+ var productToPkgMap = [ String: [ String] ] ( )
530
+ for (_, pkgBuilder) in packageIDToBuilder {
531
+ let useProductIDs = pkgBuilder. package . manifest. disambiguateByProductIDs || lookupByProductIDs
532
+ let depProductRefs = pkgBuilder. package . targets. map { $0. dependencies} . flatMap { $0} . compactMap { $0. product}
533
+ for depRef in depProductRefs {
534
+ if let depPkg = depRef. package ? . lowercased ( ) {
535
+ checkedPkgIDs. append ( depPkg)
536
+ let depProductIDs = packageIDToBuilder [ depPkg] ? . package . products. filter { $0. identity == depRef. identity } . map { useProductIDs && $0. isDefaultLibrary ? $0. identity : $0. name } ?? [ ]
537
+ for depID in depProductIDs {
538
+ productToPkgMap [ depID, default: [ ] ] . append ( depPkg)
539
+ }
540
+ } else {
541
+ let depPkgs = pkgBuilder. dependencies. filter { $0. products. contains { $0. product. name == depRef. name} } . map { $0. package . identity. description. lowercased ( ) }
542
+ productToPkgMap [ depRef. name, default: [ ] ] . append ( contentsOf: depPkgs)
543
+ checkedPkgIDs. append ( contentsOf: depPkgs)
544
+ }
545
+ }
546
+ for (depIDOrName, depPkgs) in productToPkgMap. filter ( { Set ( $0. value) . count > 1 } ) {
547
+ let name = depIDOrName. components ( separatedBy: " _ " ) . dropFirst ( ) . joined ( separator: " _ " )
548
+ throw PackageGraphError . duplicateProduct ( product: name. isEmpty ? depIDOrName : name, packages: depPkgs. sorted ( ) )
549
+ }
550
+ }
551
+
552
+ let uncheckedPkgs = packageIDToBuilder. filter { !checkedPkgIDs. contains ( $0. key) }
553
+ for (pkgID, pkgBuilder) in uncheckedPkgs {
554
+ let productIDOrNames = pkgBuilder. products. map { pkgBuilder. package . manifest. disambiguateByProductIDs && $0. product. isDefaultLibrary ? $0. product. identity : $0. product. name }
555
+ for productIDOrName in productIDOrNames {
556
+ productToPkgMap [ productIDOrName, default: [ ] ] . append ( pkgID)
557
+ }
558
+ }
559
+
560
+ let duplicates = productToPkgMap. filter ( { Set ( $0. value) . count > 1 } )
561
+ for (productName, pkgs) in duplicates {
562
+ throw PackageGraphError . duplicateProduct ( product: productName, packages: pkgs. sorted ( ) )
563
+ }
564
+ }
565
+ }
566
+
573
567
private func computePlatforms(
574
568
package : Package ,
575
569
usingXCTest: Bool ,
0 commit comments