From d0fead0ab9f6f670fe7ed9331bda1f47459c4d3d Mon Sep 17 00:00:00 2001 From: Bilal Khan Date: Sun, 15 Mar 2026 08:24:55 +0500 Subject: [PATCH 1/4] test: write the existing behavior for mismatched root path dependency --- tests/testsuite/path.rs | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 118c55409fc..15c062440df 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -1097,8 +1097,8 @@ fn invalid_base() { .with_stderr_data( "\ [ERROR] invalid character `^` in path base name: `^^not-valid^^`, the first character must be a Unicode XID start character (most letters or `_`) - - + + --> Cargo.toml:10:23 | 10 | bar = { base = '^^not-valid^^', path = 'bar' } @@ -1919,3 +1919,44 @@ foo v1.0.0 ([ROOT]/foo) "#]]) .run(); } + +#[cargo_test] +fn invalid_package_name_in_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + authors = [] + + [dependencies] + definitely_not_bar = { path = "crates/bar" } + "#, + ) + .file("src/lib.rs", "") + .file( + "crates/bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.5.0" + edition = "2015" + authors = [] + "#, + ) + .file("crates/bar/src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no matching package named `definitely_not_bar` found +location searched: [ROOT]/foo/crates/bar +required by package `foo v0.5.0 ([ROOT]/foo)` + +"#]]) + .run(); +} From f216f9bac4f09d9b1d48edba73bdac129f89418a Mon Sep 17 00:00:00 2001 From: Bilal Khan Date: Wed, 18 Mar 2026 16:11:30 +0500 Subject: [PATCH 2/4] fix: improved the current behaviour by inspecting the root package for path dependencies --- src/cargo/core/resolver/errors.rs | 115 +++++++++++++++++++++++------- src/cargo/sources/path.rs | 2 +- tests/testsuite/build.rs | 6 +- tests/testsuite/path.rs | 13 ++-- tests/testsuite/registry.rs | 6 +- 5 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/cargo/core/resolver/errors.rs b/src/cargo/core/resolver/errors.rs index b895fe3b6a6..e861cfe50b7 100644 --- a/src/cargo/core/resolver/errors.rs +++ b/src/cargo/core/resolver/errors.rs @@ -2,13 +2,14 @@ use std::fmt; use std::fmt::Write as _; use std::task::Poll; -use crate::core::{Dependency, PackageId, Registry, Summary}; -use crate::sources::IndexSummary; +use crate::core::{Dependency, PackageId, Registry, SourceId, Summary}; use crate::sources::source::QueryKind; +use crate::sources::{IndexSummary, PathSource}; use crate::util::edit_distance::{closest, edit_distance}; use crate::util::errors::CargoResult; use crate::util::{GlobalContext, OptVersionReq, VersionExt}; use anyhow::Error; +use std::path::Path; use super::context::ResolverContext; use super::types::{ConflictMap, ConflictReason}; @@ -392,33 +393,79 @@ pub(super) fn activation_error( }); let _ = writeln!(&mut msg, "perhaps you meant: {suggestions}"); } else { - let _ = writeln!( + let mut write_not_found = || { + let _ = writeln!( + &mut msg, + "no matching package named `{}` found", + dep.package_name() + ); + }; + + if dep.source_id().is_path() { + let path = dep + .source_id() + .url() + .to_file_path() + .expect("file dependency source url should have a path"); + + if let Some(context) = gctx { + let sid = dep.source_id(); + + let root_package = inspect_root_package(Path::new(&path), context, sid); + + if let Some(package_name) = root_package { + let _ = writeln!( + &mut msg, + "no matching package named `{}` found at `{}`", + dep.package_name(), + path.display() + ); + + let _ = writeln!( + &mut msg, + "note: required by {}", + describe_path_in_context(resolver_ctx, &parent.package_id()), + ); + + let _ = write!( + &mut msg, + "help: package `{}` exists at `{}`", + package_name, + path.display() + ); + } else { + write_not_found() + } + } else { + write_not_found() + } + } else { + write_not_found() + } + } + + if !dep.source_id().is_path() { + let mut location_searched_msg = registry.describe_source(dep.source_id()); + if location_searched_msg.is_empty() { + location_searched_msg = format!("{}", dep.source_id()); + } + let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg); + let _ = write!( &mut msg, - "no matching package named `{}` found", - dep.package_name() + "required by {}", + describe_path_in_context(resolver_ctx, &parent.package_id()), ); - } - let mut location_searched_msg = registry.describe_source(dep.source_id()); - if location_searched_msg.is_empty() { - location_searched_msg = format!("{}", dep.source_id()); - } - let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg); - let _ = write!( - &mut msg, - "required by {}", - describe_path_in_context(resolver_ctx, &parent.package_id()), - ); - - if let Some(gctx) = gctx { - if let Some(offline_flag) = gctx.offline_flag() { - let _ = write!( - &mut hints, - "\nAs a reminder, you're using offline mode ({offline_flag}) \ - which can sometimes cause surprising resolution failures, \ - if this error is too confusing you may wish to retry \ - without `{offline_flag}`.", - ); + if let Some(gctx) = gctx { + if let Some(offline_flag) = gctx.offline_flag() { + let _ = write!( + &mut hints, + "\nAs a reminder, you're using offline mode ({offline_flag}) \ + which can sometimes cause surprising resolution failures, \ + if this error is too confusing you may wish to retry \ + without `{offline_flag}`.", + ); + } } } @@ -575,3 +622,19 @@ pub(crate) fn describe_path<'a>( String::new() } + +fn inspect_root_package(path: &Path, gctx: &GlobalContext, sid: SourceId) -> Option { + let mut ps = PathSource::new(path, sid, gctx); + + if ps.load().is_err() { + return None; + } + + let pkg = ps + .root_package() + .expect("path source should have a root package"); + + let package_name = pkg.name().to_string(); + + Some(package_name) +} diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 8e754e03fa9..bd90debaad0 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -123,7 +123,7 @@ impl<'gctx> PathSource<'gctx> { Ok(()) } - fn read_package(&self) -> CargoResult { + pub 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) diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index eabe76ea77f..f10d44b2dd5 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -1277,9 +1277,9 @@ fn cargo_compile_with_dep_name_mismatch() { p.cargo("build") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no matching package named `notquitebar` found -location searched: [ROOT]/foo/bar -required by package `foo v0.0.1 ([ROOT]/foo)` +[ERROR] no matching package named `notquitebar` found at `[ROOT]/foo/bar` +[NOTE] required by package `foo v0.0.1 ([ROOT]/foo)` +[HELP] package `bar` exists at `[ROOT]/foo/bar` "#]]) .run(); diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 15c062440df..5342f261a8a 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -1097,8 +1097,8 @@ fn invalid_base() { .with_stderr_data( "\ [ERROR] invalid character `^` in path base name: `^^not-valid^^`, the first character must be a Unicode XID start character (most letters or `_`) - - + + --> Cargo.toml:10:23 | 10 | bar = { base = '^^not-valid^^', path = 'bar' } @@ -1673,8 +1673,7 @@ fn invalid_path_dep_in_workspace_with_lockfile() { [ERROR] no matching package found searched package name: `bar` perhaps you meant: foo -location searched: [ROOT]/foo/foo -required by package `foo v0.5.0 ([ROOT]/foo/foo)` + "#]]) .run(); @@ -1953,9 +1952,9 @@ fn invalid_package_name_in_path() { p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no matching package named `definitely_not_bar` found -location searched: [ROOT]/foo/crates/bar -required by package `foo v0.5.0 ([ROOT]/foo)` +[ERROR] no matching package named `definitely_not_bar` found at `[ROOT]/foo/crates/bar` +[NOTE] required by package `foo v0.5.0 ([ROOT]/foo)` +[HELP] package `bar` exists at `[ROOT]/foo/crates/bar` "#]]) .run(); diff --git a/tests/testsuite/registry.rs b/tests/testsuite/registry.rs index 896bcc3432f..1d3d0108711 100644 --- a/tests/testsuite/registry.rs +++ b/tests/testsuite/registry.rs @@ -2207,8 +2207,7 @@ fn use_semver_package_incorrectly_http() { use_semver_package_incorrectly(str![[r#" [ERROR] failed to select a version for the requirement `a = "^0.1"` candidate versions found which didn't match: 0.1.1-alpha.0 -location searched: [ROOT]/foo/a -required by package `b v0.1.0 ([ROOT]/foo/b)` + if you are looking for the prerelease package it needs to be specified explicitly a = { version = "0.1.1-alpha.0" } @@ -2220,8 +2219,7 @@ fn use_semver_package_incorrectly_git() { use_semver_package_incorrectly(str![[r#" [ERROR] failed to select a version for the requirement `a = "^0.1"` candidate versions found which didn't match: 0.1.1-alpha.0 -location searched: [ROOT]/foo/a -required by package `b v0.1.0 ([ROOT]/foo/b)` + if you are looking for the prerelease package it needs to be specified explicitly a = { version = "0.1.1-alpha.0" } From 29c2c508a5eea04251847237f95e66941f8dcfa4 Mon Sep 17 00:00:00 2001 From: Bilal Khan Date: Wed, 18 Mar 2026 19:55:03 +0500 Subject: [PATCH 3/4] test: writes the existing behavior for mismatched sudirectory path dependency --- tests/testsuite/path.rs | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 5342f261a8a..02605dc43cf 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -1959,3 +1959,54 @@ fn invalid_package_name_in_path() { "#]]) .run(); } + +#[cargo_test] +fn invalid_package_in_subdirectory() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + authors = [] + + [dependencies] + definitely_not_bar = { path = "crates/bar" } + "#, + ) + .file("src/lib.rs", "") + .file( + "crates/bar/definitely_not_bar/Cargo.toml", + r#" + [package] + name = "definitely_not_bar" + version = "0.5.0" + edition = "2015" + authors = [] + "#, + ) + .file("crates/bar/definitely_not_bar/src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to get `definitely_not_bar` as a dependency of package `foo v0.5.0 ([ROOT]/foo)` + +Caused by: + failed to load source for dependency `definitely_not_bar` + +Caused by: + Unable to update [ROOT]/foo/crates/bar + +Caused by: + failed to read `[ROOT]/foo/crates/bar/Cargo.toml` + +Caused by: + [NOT_FOUND] + +"#]]) + .run(); +} From 951c11967836598dec47584e71808d154cde63a4 Mon Sep 17 00:00:00 2001 From: Bilal Khan Date: Sun, 22 Mar 2026 12:05:41 +0500 Subject: [PATCH 4/4] fix: improved the current behaviour by inspecting the recursive packages for path dependencies --- src/cargo/core/resolver/errors.rs | 61 ++++++++++++++++++++++++++++++- tests/testsuite/path.rs | 16 ++------ 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/cargo/core/resolver/errors.rs b/src/cargo/core/resolver/errors.rs index e861cfe50b7..940ed4faf88 100644 --- a/src/cargo/core/resolver/errors.rs +++ b/src/cargo/core/resolver/errors.rs @@ -4,12 +4,12 @@ use std::task::Poll; use crate::core::{Dependency, PackageId, Registry, SourceId, Summary}; use crate::sources::source::QueryKind; -use crate::sources::{IndexSummary, PathSource}; +use crate::sources::{IndexSummary, PathSource, RecursivePathSource}; use crate::util::edit_distance::{closest, edit_distance}; use crate::util::errors::CargoResult; use crate::util::{GlobalContext, OptVersionReq, VersionExt}; use anyhow::Error; -use std::path::Path; +use std::path::{Path, PathBuf}; use super::context::ResolverContext; use super::types::{ConflictMap, ConflictReason}; @@ -412,6 +412,10 @@ pub(super) fn activation_error( let sid = dep.source_id(); let root_package = inspect_root_package(Path::new(&path), context, sid); + let requested = dep.package_name().as_str(); + + let recursive_packages = + inspect_recursive_packages(Path::new(&path), context, sid, requested); if let Some(package_name) = root_package { let _ = writeln!( @@ -433,6 +437,26 @@ pub(super) fn activation_error( package_name, path.display() ); + } else if let Some((package_name, package_path)) = recursive_packages { + let _ = writeln!( + &mut msg, + "no matching package named `{}` found at `{}`", + dep.package_name(), + path.display() + ); + + let _ = writeln!( + &mut msg, + "note: required by {}", + describe_path_in_context(resolver_ctx, &parent.package_id()), + ); + + let _ = write!( + &mut msg, + "help: package `{}` exists at `{}`", + package_name, + package_path.display() + ); } else { write_not_found() } @@ -638,3 +662,36 @@ fn inspect_root_package(path: &Path, gctx: &GlobalContext, sid: SourceId) -> Opt Some(package_name) } + +fn inspect_recursive_packages( + path: &Path, + gctx: &GlobalContext, + sid: SourceId, + requested: &str, +) -> Option<(String, PathBuf)> { + let mut rps = RecursivePathSource::new(path, sid, gctx); + + if rps.load().is_err() { + return None; + } + + let pkgs = rps + .read_packages() + .expect("path source should read the packages"); + + for pkg in pkgs { + if pkg.name() == requested { + let manifest = pkg.manifest_path(); + let pkg_dir = manifest + .parent() + .expect("manifest path should have a parent") + .to_path_buf(); + + let name = pkg.name().to_string(); + let path = pkg_dir; + + return Some((name, path)); + } + } + return None; +} diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 02605dc43cf..5c5dafaa424 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -1993,19 +1993,9 @@ fn invalid_package_in_subdirectory() { p.cargo("generate-lockfile") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] failed to get `definitely_not_bar` as a dependency of package `foo v0.5.0 ([ROOT]/foo)` - -Caused by: - failed to load source for dependency `definitely_not_bar` - -Caused by: - Unable to update [ROOT]/foo/crates/bar - -Caused by: - failed to read `[ROOT]/foo/crates/bar/Cargo.toml` - -Caused by: - [NOT_FOUND] +[ERROR] no matching package named `definitely_not_bar` found at `[ROOT]/foo/crates/bar` +[NOTE] required by package `foo v0.5.0 ([ROOT]/foo)` +[HELP] package `bar` exists at `[ROOT]/foo/crates/bar/definitely_not_bar` "#]]) .run();