Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/cargo/core/resolver/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt;
use std::fmt::Write as _;
use std::path::PathBuf;

use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::sources::IndexSummary;
Expand Down Expand Up @@ -360,6 +361,51 @@ pub(super) fn activation_error(
"\nnote: perhaps a crate was updated and forgotten to be re-vendored?"
);
}
} else if let Some(packages) = alt_paths(dep, gctx) {
let path = dep.source_id().url().to_file_path().unwrap();
let _ = writeln!(
&mut msg,
"no matching package named `{}` found",
dep.package_name()
);
Comment on lines +366 to +370
Copy link
Copy Markdown
Contributor

@epage epage Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does is_handled_custom_path_error till exist?

On the surface, it looks like we use it to move the location searched: message into the line before it but it isn't clear why we feel that complexity is worth it.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It prevents location searched from appearing in the alt_paths branch without it, location searched would also appear for path hint errors where it's redundant since we already show the path in the error message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was what my second paragraph was about which ends with

but it isn't clear why we feel that complexity is worth it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed! location searched now appears in all branches including alt_paths


let mut exact_match: Option<PathBuf> = None;
let mut found_dir: Option<String> = None;
let mut names_found: Vec<(String, PathBuf)> = vec![];

for pkg in &packages {
let manifest_dir = pkg.manifest_path().parent().unwrap();
let p_name = pkg.name().as_str();
if p_name == dep.package_name().as_str() {
exact_match = Some(manifest_dir.to_path_buf());
break;
} else if manifest_dir == path {
found_dir = Some(p_name.to_string());
} else {
names_found.push((p_name.to_string(), manifest_dir.to_path_buf()));
}
}

let mut add_hint = |name: &str, p: &std::path::Path| {
let _ = writeln!(&mut hints);
let _ = write!(
&mut hints,
"help: package `{}` exists at `{}`",
name,
p.display()
);
};

if let Some(dir) = exact_match {
add_hint(dep.package_name().as_str(), &dir);
} else if let Some(dir_pkg) = found_dir {
add_hint(&dir_pkg, &path);
} else {
names_found.sort_by(|a, b| a.0.cmp(&b.0));
for (name, p) in names_found.iter().take(3) {
add_hint(name, p);
}
}
} else if let Some(name_candidates) = alt_names(registry, dep) {
let name_candidates = match name_candidates {
Ok(c) => c,
Expand Down Expand Up @@ -497,6 +543,29 @@ fn alt_names(
}
}

/// For path dependencies, scan the dependency directory for any packages
/// that exist in subdirectories. This helps when the user points to a
/// directory without a Cargo.toml or with the wrong package name.
fn alt_paths(dep: &Dependency, gctx: Option<&GlobalContext>) -> Option<Vec<crate::core::Package>> {
let gctx = gctx?;
if !dep.source_id().is_path() {
return None;
}
let path = dep.source_id().url().to_file_path().ok()?;
if !path.is_dir() {
return None;
}
use crate::sources::path::RecursivePathSource;
let source_id = dep.source_id();
let mut source = RecursivePathSource::new(&path, source_id, gctx);
let packages = source.read_packages().ok()?;
if packages.is_empty() {
None
} else {
Some(packages)
}
}

/// Returns String representation of dependency chain for a particular `pkgid`
/// within given context.
pub(super) fn describe_path_in_context(cx: &ResolverContext, id: &PackageId) -> String {
Expand Down
56 changes: 45 additions & 11 deletions src/cargo/sources/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub struct PathSource<'gctx> {
path: PathBuf,
/// Packages that this sources has discovered.
package: RefCell<Option<Package>>,
/// Whether the source has been loaded.
loaded: Cell<bool>,
gctx: &'gctx GlobalContext,
}

Expand All @@ -51,6 +53,7 @@ impl<'gctx> PathSource<'gctx> {
source_id,
path: path.to_path_buf(),
package: RefCell::new(None),
loaded: Cell::new(false),
gctx,
}
}
Expand All @@ -64,6 +67,7 @@ impl<'gctx> PathSource<'gctx> {
source_id,
path,
package: RefCell::new(Some(pkg)),
loaded: Cell::new(true),
gctx,
}
}
Expand All @@ -76,10 +80,14 @@ impl<'gctx> PathSource<'gctx> {

match &*self.package.borrow() {
Some(pkg) => Ok(pkg.clone()),
None => Err(internal(format!(
"no package found in source {:?}",
self.path
))),
None => {
let path = self.path.join("Cargo.toml");
ops::read_package(&path, self.source_id, self.gctx)?;
Err(internal(format!(
"no package found in source {:?}",
self.path
)))
}
}
}

Expand Down Expand Up @@ -116,18 +124,44 @@ impl<'gctx> PathSource<'gctx> {

/// Discovers packages inside this source if it hasn't yet done.
pub fn load(&self) -> CargoResult<()> {
let mut package = self.package.borrow_mut();
if package.is_none() {
*package = Some(self.read_package()?);
if !self.loaded.get() {
self.loaded.set(true);
*self.package.borrow_mut() = self.read_package()?;
}

Ok(())
}

fn read_package(&self) -> CargoResult<Package> {
/// Reads the package from the manifest file.
///
/// Returns `Ok(None)` if `Cargo.toml` does not exist at the source path.
/// Other errors (permissions, malformed TOML, etc.) still propagate.
fn read_package(&self) -> CargoResult<Option<Package>> {
let path = self.path.join("Cargo.toml");
let pkg = ops::read_package(&path, self.source_id, self.gctx)?;
Ok(pkg)
match ops::read_package(&path, self.source_id, self.gctx) {
Ok(pkg) => Ok(Some(pkg)),
Err(e) => {
if e.downcast_ref::<io::Error>()
.map_or(false, |io_err| io_err.kind() == io::ErrorKind::NotFound)
{
return Ok(None);
}
// Walk the error chain for ManifestError wrapping NotFound.
for cause in e.chain() {
if let Some(manifest_err) =
cause.downcast_ref::<crate::util::errors::ManifestError>()
{
if let Some(io_err) = std::error::Error::source(manifest_err)
.and_then(|s| s.downcast_ref::<io::Error>())
{
if io_err.kind() == io::ErrorKind::NotFound {
return Ok(None);
}
}
}
}
Err(e)
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/testsuite/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,7 @@ fn cargo_compile_with_dep_name_mismatch() {
[ERROR] no matching package named `notquitebar` found
location searched: [ROOT]/foo/bar
required by package `foo v0.0.1 ([ROOT]/foo)`
[HELP] package `bar` exists at `[ROOT]/foo/bar`
"#]])
.run();
Expand Down
22 changes: 2 additions & 20 deletions tests/testsuite/cargo_add/invalid_path/stderr.term.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading