Skip to content

Commit 664826f

Browse files
mininnyMaxDesiatov
authored andcommitted
Add fix-it when there's a package target with similar name as the one provided in the dependency (#6897)
Update error message when there's a package target with a similar name as the one provided in the dependency. ### Motivation: Partially addresses #4803. It adds a suggestion for an alternative dependency name when there's a dependency target with similar name as the one provided in the `Package.swift`. ### Modifications: Use the existing `bestMatch(for, from)` from TSCBasic to compare the `productRef.name` and `allTargetNames`. However, I'm not sure how and if I can include target from system packages. ### Result: Error messages for package target not found may also suggest an alternative name like: `product 'Barx' required by package 'foo' target 'FooTarget' not found. Did you mean 'Bar'?`
1 parent 7aefb96 commit 664826f

File tree

3 files changed

+81
-5
lines changed

3 files changed

+81
-5
lines changed

Sources/PackageGraph/PackageGraph+Loading.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import PackageLoading
1616
import PackageModel
1717

1818
import func TSCBasic.topologicalSort
19+
import func TSCBasic.bestMatch
1920

2021
extension PackageGraph {
2122

@@ -495,13 +496,16 @@ private func createResolvedPackages(
495496
}.map {$0.targets}.flatMap{$0}.filter { t in
496497
t.name != productRef.name
497498
}
498-
499+
500+
// Find a product name from the available product dependencies that is most similar to the required product name.
501+
let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames))
499502
let error = PackageGraphError.productDependencyNotFound(
500503
package: package.identity.description,
501504
targetName: targetBuilder.target.name,
502505
dependencyProductName: productRef.name,
503506
dependencyPackageName: productRef.package,
504-
dependencyProductInDecl: !declProductsAsDependency.isEmpty
507+
dependencyProductInDecl: !declProductsAsDependency.isEmpty,
508+
similarProductName: bestMatchedProductName
505509
)
506510
packageObservabilityScope.emit(error)
507511
}

Sources/PackageGraph/PackageGraph.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ enum PackageGraphError: Swift.Error {
2323
case cycleDetected((path: [Manifest], cycle: [Manifest]))
2424

2525
/// The product dependency not found.
26-
case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool)
26+
case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?)
2727

2828
/// The package dependency already satisfied by a different dependency package
2929
case dependencyAlreadySatisfiedByIdentifier(package: String, dependencyLocation: String, otherDependencyURL: String, identity: PackageIdentity)
@@ -219,11 +219,15 @@ extension PackageGraphError: CustomStringConvertible {
219219
(cycle.path + cycle.cycle).map({ $0.displayName }).joined(separator: " -> ") +
220220
" -> " + cycle.cycle[0].displayName
221221

222-
case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl):
222+
case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName):
223223
if dependencyProductInDecl {
224224
return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'."
225225
} else {
226-
return "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")."
226+
var description = "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")."
227+
if let similarProductName {
228+
description += " Did you mean '\(similarProductName)'?"
229+
}
230+
return description
227231
}
228232
case .dependencyAlreadySatisfiedByIdentifier(let package, let dependencyURL, let otherDependencyURL, let identity):
229233
return "'\(package)' dependency on '\(dependencyURL)' conflicts with dependency on '\(otherDependencyURL)' which has the same identity '\(identity)'"

Tests/PackageGraphTests/PackageGraphTests.swift

+68
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,74 @@ class PackageGraphTests: XCTestCase {
786786
}
787787
}
788788

789+
func testProductDependencyWithSimilarName() throws {
790+
let fs = InMemoryFileSystem(emptyFiles:
791+
"/Foo/Sources/Foo/foo.swift",
792+
"/Bar/Sources/Bar/bar.swift"
793+
)
794+
795+
let observability = ObservabilitySystem.makeForTesting()
796+
_ = try loadPackageGraph(
797+
fileSystem: fs,
798+
manifests: [
799+
Manifest.createRootManifest(
800+
displayName: "Foo",
801+
path: "/Foo",
802+
targets: [
803+
TargetDescription(name: "Foo", dependencies: ["Barx"]),
804+
]),
805+
Manifest.createRootManifest(
806+
displayName: "Bar",
807+
path: "/Bar",
808+
targets: [
809+
TargetDescription(name: "Bar")
810+
]),
811+
],
812+
observabilityScope: observability.topScope
813+
)
814+
815+
testDiagnostics(observability.diagnostics) { result in
816+
result.check(
817+
diagnostic: "product 'Barx' required by package 'foo' target 'Foo' not found. Did you mean 'Bar'?",
818+
severity: .error
819+
)
820+
}
821+
}
822+
823+
func testProductDependencyWithNonSimilarName() throws {
824+
let fs = InMemoryFileSystem(emptyFiles:
825+
"/Foo/Sources/Foo/foo.swift",
826+
"/Bar/Sources/Bar/bar.swift"
827+
)
828+
829+
let observability = ObservabilitySystem.makeForTesting()
830+
_ = try loadPackageGraph(
831+
fileSystem: fs,
832+
manifests: [
833+
Manifest.createRootManifest(
834+
displayName: "Foo",
835+
path: "/Foo",
836+
targets: [
837+
TargetDescription(name: "Foo", dependencies: ["Qux"]),
838+
]),
839+
Manifest.createRootManifest(
840+
displayName: "Bar",
841+
path: "/Bar",
842+
targets: [
843+
TargetDescription(name: "Bar")
844+
]),
845+
],
846+
observabilityScope: observability.topScope
847+
)
848+
849+
testDiagnostics(observability.diagnostics) { result in
850+
result.check(
851+
diagnostic: "product 'Qux' required by package 'foo' target 'Foo' not found.",
852+
severity: .error
853+
)
854+
}
855+
}
856+
789857
func testProductDependencyDeclaredInSamePackage() throws {
790858
let fs = InMemoryFileSystem(emptyFiles:
791859
"/Foo/Sources/FooTarget/src.swift",

0 commit comments

Comments
 (0)