diff --git a/src/cargo/core/resolver/errors.rs b/src/cargo/core/resolver/errors.rs index cab65502f38..6458def7a84 100644 --- a/src/cargo/core/resolver/errors.rs +++ b/src/cargo/core/resolver/errors.rs @@ -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; @@ -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() + ); + + let mut exact_match: Option = None; + let mut found_dir: Option = 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, @@ -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> { + 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 { diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 78dbe2027ee..74399fc9353 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -38,6 +38,8 @@ pub struct PathSource<'gctx> { path: PathBuf, /// Packages that this sources has discovered. package: RefCell>, + /// Whether the source has been loaded. + loaded: Cell, gctx: &'gctx GlobalContext, } @@ -51,6 +53,7 @@ impl<'gctx> PathSource<'gctx> { source_id, path: path.to_path_buf(), package: RefCell::new(None), + loaded: Cell::new(false), gctx, } } @@ -64,6 +67,7 @@ impl<'gctx> PathSource<'gctx> { source_id, path, package: RefCell::new(Some(pkg)), + loaded: Cell::new(true), gctx, } } @@ -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 + ))) + } } } @@ -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 { + /// 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> { 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::() + .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::() + { + if let Some(io_err) = std::error::Error::source(manifest_err) + .and_then(|s| s.downcast_ref::()) + { + if io_err.kind() == io::ErrorKind::NotFound { + return Ok(None); + } + } + } + } + Err(e) + } + } } } diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index d28efeace62..e60c094dfb9 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -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(); diff --git a/tests/testsuite/cargo_add/invalid_path/stderr.term.svg b/tests/testsuite/cargo_add/invalid_path/stderr.term.svg index 203ba67dc93..f82f0733b67 100644 --- a/tests/testsuite/cargo_add/invalid_path/stderr.term.svg +++ b/tests/testsuite/cargo_add/invalid_path/stderr.term.svg @@ -1,4 +1,4 @@ - +