@@ -685,31 +685,11 @@ private func createResolvedPackages(
685
685
// found errors when there are more important errors to
686
686
// resolve (like authentication issues).
687
687
if !observabilityScope. errorsReportedInAnyScope {
688
- // Emit error if a product (not module) declared in the package is also a productRef (dependency)
689
- let declProductsAsDependency = package . products. filter { product in
690
- lookupByProductIDs ? product. identity == productRef. identity : product. name == productRef. name
691
- } . map { $0. modules} . flatMap { $0} . filter { t in
692
- t. name != productRef. name
693
- }
694
-
695
- // Find a product name from the available product dependencies that is most similar to the required product name.
696
- let bestMatchedProductName = bestMatch ( for: productRef. name, from: Array ( allModuleNames) )
697
- var packageContainingBestMatchedProduct : String ?
698
- if let bestMatchedProductName, productRef. name == bestMatchedProductName {
699
- let dependentPackages = packageBuilder. dependencies. map ( \. package )
700
- for p in dependentPackages where p. modules. contains ( where: { $0. name == bestMatchedProductName } ) {
701
- packageContainingBestMatchedProduct = p. identity. description
702
- break
703
- }
704
- }
705
- let error = PackageGraphError . productDependencyNotFound (
706
- package : package . identity. description,
707
- moduleName: moduleBuilder. module. name,
708
- dependencyProductName: productRef. name,
709
- dependencyPackageName: productRef. package ,
710
- dependencyProductInDecl: !declProductsAsDependency. isEmpty,
711
- similarProductName: bestMatchedProductName,
712
- packageContainingSimilarProduct: packageContainingBestMatchedProduct
688
+ let error = prepareProductDependencyNotFoundError (
689
+ packageBuilder: packageBuilder,
690
+ moduleBuilder: moduleBuilder,
691
+ dependency: productRef,
692
+ lookupByProductIDs: lookupByProductIDs
713
693
)
714
694
packageObservabilityScope. emit ( error)
715
695
}
@@ -838,6 +818,110 @@ private func createResolvedPackages(
838
818
return IdentifiableSet ( try packageBuilders. map { try $0. construct ( ) } )
839
819
}
840
820
821
+ private func prepareProductDependencyNotFoundError(
822
+ packageBuilder: ResolvedPackageBuilder ,
823
+ moduleBuilder: ResolvedModuleBuilder ,
824
+ dependency: Module . ProductReference ,
825
+ lookupByProductIDs: Bool
826
+ ) -> PackageGraphError {
827
+ let packageName = packageBuilder. package . identity. description
828
+ // Module's dependency is either a local module or a product from another package.
829
+ // If dependency is a product from the current package, that's an incorrect
830
+ // declaration of the dependency and we should show relevant error. Let's see
831
+ // if indeed the dependency matches any of the products.
832
+ let declProductsAsDependency = packageBuilder. package . products. filter { product in
833
+ lookupByProductIDs ? product. identity == dependency. identity : product. name == dependency. name
834
+ } . flatMap ( \. modules) . filter { t in
835
+ t. name != dependency. name
836
+ }
837
+ if !declProductsAsDependency. isEmpty {
838
+ return PackageGraphError . productDependencyNotFound (
839
+ package : packageName,
840
+ moduleName: moduleBuilder. module. name,
841
+ dependencyProductName: dependency. name,
842
+ dependencyPackageName: dependency. package ,
843
+ dependencyProductInDecl: true ,
844
+ similarProductName: nil ,
845
+ packageContainingSimilarProduct: nil
846
+ )
847
+ }
848
+
849
+ // If dependency name is a typo, find best possible match from the available destinations.
850
+ // Depending on how the dependency is declared, "available destinations" might be:
851
+ // - modules within the current package
852
+ // - products across all packages in the graph
853
+ // - products from a specific package
854
+ var packageContainingBestMatchedProduct : String ?
855
+ var bestMatchedProductName : String ?
856
+ if dependency. package == nil {
857
+ // First assume it's a dependency on modules within the same package.
858
+ let localModules = Array ( packageBuilder. modules. map ( \. module. name) . filter { $0 != moduleBuilder. module. name } )
859
+ bestMatchedProductName = bestMatch ( for: dependency. name, from: localModules)
860
+ if bestMatchedProductName != nil {
861
+ return PackageGraphError . productDependencyNotFound (
862
+ package : packageName,
863
+ moduleName: moduleBuilder. module. name,
864
+ dependencyProductName: dependency. name,
865
+ dependencyPackageName: nil ,
866
+ dependencyProductInDecl: false ,
867
+ similarProductName: bestMatchedProductName,
868
+ packageContainingSimilarProduct: nil
869
+ )
870
+ }
871
+ // Since there's no package name in the dependency declaration, and no match across
872
+ // the local modules, we assume the user actually meant to use product dependency,
873
+ // but didn't specify package to use the product from. Since products are globally
874
+ // unique, we should be able to find a good match across the graph, if the package
875
+ // is already a part of the dependency tree.
876
+ let availableProducts = Dictionary (
877
+ uniqueKeysWithValues: packageBuilder. dependencies
878
+ . flatMap { ( packageDep: ResolvedPackageBuilder ) -> [ (
879
+ String ,
880
+ String
881
+ ) ] in
882
+ let manifestProducts = packageDep. package . manifest. products. map ( \. name)
883
+ let explicitProducts = packageDep. package . products. filter { manifestProducts. contains ( $0. name) }
884
+ let explicitIdsOrNames = Set ( explicitProducts. map { lookupByProductIDs ? $0. identity : $0. name } )
885
+ return explicitIdsOrNames. map { ( $0, packageDep. package . identity. description) }
886
+ }
887
+ )
888
+ bestMatchedProductName = bestMatch ( for: dependency. name, from: Array ( availableProducts. keys) )
889
+ if bestMatchedProductName != nil {
890
+ packageContainingBestMatchedProduct = availableProducts [ bestMatchedProductName!]
891
+ }
892
+ return PackageGraphError . productDependencyNotFound (
893
+ package : packageName,
894
+ moduleName: moduleBuilder. module. name,
895
+ dependencyProductName: dependency. name,
896
+ dependencyPackageName: nil ,
897
+ dependencyProductInDecl: false ,
898
+ similarProductName: bestMatchedProductName,
899
+ packageContainingSimilarProduct: packageContainingBestMatchedProduct
900
+ )
901
+ } else {
902
+ // Package is explicitly listed in the product dependency, we shall search
903
+ // within the products from that package.
904
+ let availableProducts = packageBuilder. dependencies
905
+ . filter { $0. package . identity. description == dependency. package }
906
+ . flatMap { ( packageDep: ResolvedPackageBuilder ) -> [ String ] in
907
+ let manifestProducts = packageDep. package . manifest. products. map ( \. name)
908
+ let explicitProducts = packageDep. package . products. filter { manifestProducts. contains ( $0. name) }
909
+ let explicitIdsOrNames = Set ( explicitProducts. map { lookupByProductIDs ? $0. identity : $0. name } )
910
+ return Array ( explicitIdsOrNames)
911
+ }
912
+ bestMatchedProductName = bestMatch ( for: dependency. name, from: availableProducts)
913
+ return PackageGraphError . productDependencyNotFound (
914
+ package : packageName,
915
+ moduleName: moduleBuilder. module. name,
916
+ dependencyProductName: dependency. name,
917
+ dependencyPackageName: dependency. package ,
918
+ dependencyProductInDecl: false ,
919
+ similarProductName: bestMatchedProductName,
920
+ packageContainingSimilarProduct: dependency. package
921
+ )
922
+ }
923
+ }
924
+
841
925
private func emitDuplicateProductDiagnostic(
842
926
productName: String ,
843
927
packages: [ Package ] ,
0 commit comments