Skip to content

Commit

Permalink
Improve error message about the PackageGraphError.productDependencyNo…
Browse files Browse the repository at this point in the history
…tFound (#7419)

Fixed a bug in the productDependencyNotFound error message

### Motivation:

fix #7398

The above issue suggests the following two defects.

1. package dependency resolution changes depending on the order of
packages (alphabetical order)
2. the phrase "Did you meen..." in the error message in the error
message is not on target.


### Modifications:

The first problem is as described in the issue, if users rename the
directory containing Package.swift from repro to zzz, the "Did you
meen..." will appear. will appear. Essentially, the error minutes should
be displayed in full, regardless of the name of the directory. This is
due to the fact that the loop used to resolve dependencies depends on
the alphabetical order of the directories and the side effect on
allTargetName inside the loop. Therefore, the side effect for
allTargetName inside the loop has been moved to the outside of the loop.

The second problem is that when dependency A of a package is not found,
a strange suggestion "Did you meen `A`? ", which is a strange
suggestion. This is because when a dependency is not found, the message
"Did you meen `<target name>`" is printed if there is a dependency with
a similar name (even the exact same name). Thus, if the names are the
same, "Did you meen `.product(name: ... , package:
"swift-argugument")"`" instead of the found target name.

### Result:

Command to execute: 
`swift build`

Result of command:.
`error: 'repro': product 'ArgumentParser' required by package 'repro'
target 'repro' not found. Did you mean '.product(name: "ArgumentParser",
package: "swift-argument-parser")'?`

Condition:
The following steps were taken to create the environment.

1. mkdir repro
1. cd repro
1. swift package init --type executable
1. Open Package.swift and make sure it has this content:

```swift
// swift-tools-version: 5.10

import PackageDescription

let package = Package(
    name: "repro",
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0")
    ],
    targets: [
        .executableTarget(
            name: "repro",
            dependencies: ["ArgumentParser"]
        )
    ]
)
```

---------

Co-authored-by: Yuta Saito <[email protected]>
  • Loading branch information
k-kohey and kateinoigakukun committed Mar 30, 2024
1 parent 9386170 commit ea1730b
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 8 deletions.
23 changes: 18 additions & 5 deletions Sources/PackageGraph/ModulesGraph+Loading.swift
Expand Up @@ -434,6 +434,13 @@ private func createResolvedPackages(
// Track if multiple targets are found with the same name.
var foundDuplicateTarget = false

for packageBuilder in packageBuilders {
for target in packageBuilder.targets {
// Record if we see a duplicate target.
foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(target.target.name).inserted
}
}

// Do another pass and establish product dependencies of each target.
for packageBuilder in packageBuilders {
let package = packageBuilder.package
Expand Down Expand Up @@ -493,9 +500,6 @@ private func createResolvedPackages(

// Establish dependencies in each target.
for targetBuilder in packageBuilder.targets {
// Record if we see a duplicate target.
foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(targetBuilder.target.name).inserted

// Directly add all the system module dependencies.
targetBuilder.dependencies += implicitSystemTargetDeps.map { .target($0, conditions: []) }

Expand Down Expand Up @@ -524,13 +528,22 @@ private func createResolvedPackages(
} else {
// Find a product name from the available product dependencies that is most similar to the required product name.
let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames))
var packageContainingBestMatchedProduct: String?
if let bestMatchedProductName, productRef.name == bestMatchedProductName {
let dependentPackages = packageBuilder.dependencies.map(\.package)
for p in dependentPackages where p.targets.contains(where: { $0.name == bestMatchedProductName }) {
packageContainingBestMatchedProduct = p.identity.description
break
}
}
let error = PackageGraphError.productDependencyNotFound(
package: package.identity.description,
targetName: targetBuilder.target.name,
dependencyProductName: productRef.name,
dependencyPackageName: productRef.package,
dependencyProductInDecl: !declProductsAsDependency.isEmpty,
similarProductName: bestMatchedProductName
similarProductName: bestMatchedProductName,
packageContainingSimilarProduct: packageContainingBestMatchedProduct
)
packageObservabilityScope.emit(error)
}
Expand Down Expand Up @@ -568,7 +581,7 @@ private func createResolvedPackages(
// If a target with similar name was encountered before, we emit a diagnostic.
if foundDuplicateTarget {
var duplicateTargets = [String: [Package]]()
for targetName in allTargetNames.sorted() {
for targetName in Set(allTargetNames).sorted() {
let packages = packageBuilders
.filter({ $0.targets.contains(where: { $0.target.name == targetName }) })
.map{ $0.package }
Expand Down
8 changes: 5 additions & 3 deletions Sources/PackageGraph/ModulesGraph.swift
Expand Up @@ -23,7 +23,7 @@ enum PackageGraphError: Swift.Error {
case cycleDetected((path: [Manifest], cycle: [Manifest]))

/// The product dependency not found.
case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?)
case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?, packageContainingSimilarProduct: String?)

/// The package dependency already satisfied by a different dependency package
case dependencyAlreadySatisfiedByIdentifier(package: String, dependencyLocation: String, otherDependencyURL: String, identity: PackageIdentity)
Expand Down Expand Up @@ -230,12 +230,14 @@ extension PackageGraphError: CustomStringConvertible {
(cycle.path + cycle.cycle).map({ $0.displayName }).joined(separator: " -> ") +
" -> " + cycle.cycle[0].displayName

case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName):
case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName, let packageContainingSimilarProduct):
if dependencyProductInDecl {
return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'."
} else {
var description = "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")."
if let similarProductName {
if let similarProductName, let packageContainingSimilarProduct {
description += " Did you mean '.product(name: \"\(similarProductName)\", package: \"\(packageContainingSimilarProduct)\")'?"
} else if let similarProductName {
description += " Did you mean '\(similarProductName)'?"
}
return description
Expand Down
51 changes: 51 additions & 0 deletions Tests/PackageGraphTests/ModulesGraphTests.swift
Expand Up @@ -2676,6 +2676,57 @@ final class ModulesGraphTests: XCTestCase {

XCTAssertEqual(observability.diagnostics.count, 0, "unexpected diagnostics: \(observability.diagnostics.map { $0.description })")
}

func testDependencyResolutionWithErrorMessages() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/aaa/Sources/aaa/main.swift",
"/zzz/Sources/zzz/source.swift"
)

let observability = ObservabilitySystem.makeForTesting()
let _ = try loadModulesGraph(
fileSystem: fs,
manifests: [
Manifest.createRootManifest(
displayName: "aaa",
path: "/aaa",
dependencies: [
.localSourceControl(path: "/zzz", requirement: .upToNextMajor(from: "1.0.0"))
],
products: [],
targets: [
TargetDescription(
name: "aaa",
dependencies: ["zzy"],
type: .executable
)
]),
Manifest.createRootManifest(
displayName: "zzz",
path: "/zzz",
products: [
ProductDescription(
name: "zzz",
type: .library(.automatic),
targets: ["zzz"]
)
],
targets: [
TargetDescription(
name: "zzz"
)
])
],
observabilityScope: observability.topScope
)

testDiagnostics(observability.diagnostics) { result in
result.check(
diagnostic: "product 'zzy' required by package 'aaa' target 'aaa' not found. Did you mean 'zzz'?",
severity: .error
)
}
}
}


Expand Down

0 comments on commit ea1730b

Please sign in to comment.