diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index b059be395..a89ea94f4 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -374,6 +374,7 @@ pub fn create_bcx<'a, 'gctx>( let generator = UnitGenerator { ws, packages: &to_builds, + spec, target_data: &target_data, filter, requested_kinds: &build_config.requested_kinds, diff --git a/src/cargo/ops/cargo_compile/unit_generator.rs b/src/cargo/ops/cargo_compile/unit_generator.rs index ae1f3bc56..2075cd879 100644 --- a/src/cargo/ops/cargo_compile/unit_generator.rs +++ b/src/cargo/ops/cargo_compile/unit_generator.rs @@ -17,6 +17,7 @@ use crate::util::{closest_msg, CargoResult}; use super::compile_filter::{CompileFilter, FilterRule, LibRule}; use super::packages::build_glob; +use super::Packages; /// A proposed target. /// @@ -47,6 +48,7 @@ struct Proposal<'a> { pub(super) struct UnitGenerator<'a, 'gctx> { pub ws: &'a Workspace<'gctx>, pub packages: &'a [&'a Package], + pub spec: &'a Packages, pub target_data: &'a RustcTargetData<'gctx>, pub filter: &'a CompileFilter, pub requested_kinds: &'a [CompileKind], @@ -247,15 +249,15 @@ impl<'a> UnitGenerator<'a, '_> { mode: CompileMode, ) -> CargoResult>> { let is_glob = is_glob_pattern(target_name); - let proposals = if is_glob { - let pattern = build_glob(target_name)?; - let filter = |t: &Target| is_expected_kind(t) && pattern.matches(t.name()); - self.filter_targets(filter, true, mode) - } else { - let filter = |t: &Target| t.name() == target_name && is_expected_kind(t); - self.filter_targets(filter, true, mode) + let pattern = build_glob(target_name)?; + let filter = |t: &Target| { + if is_glob { + is_expected_kind(t) && pattern.matches(t.name()) + } else { + is_expected_kind(t) && t.name() == target_name + } }; - + let proposals = self.filter_targets(filter, true, mode); if proposals.is_empty() { let targets = self .packages @@ -267,35 +269,103 @@ impl<'a> UnitGenerator<'a, '_> { }) .collect::>(); let suggestion = closest_msg(target_name, targets.iter(), |t| t.name(), "target"); + let targets_elsewhere = self.get_targets_from_other_packages(filter)?; + let need_append_targets_elsewhere = !targets_elsewhere.is_empty(); + let append_targets_elsewhere = |msg: &mut String, prefix: &str| { + let mut available_msg = Vec::new(); + for (package, targets) in targets_elsewhere { + if !targets.is_empty() { + available_msg.push(format!( + "help: Available {target_desc} in `{package}` package:" + )); + for target in targets { + available_msg.push(format!(" {target}")); + } + } + } + if !available_msg.is_empty() { + write!(msg, "{prefix}{}", available_msg.join("\n"))?; + } + CargoResult::Ok(()) + }; + + let unmatched_packages = || match self.spec { + Packages::Default | Packages::OptOut(_) | Packages::All(_) => { + "default-run packages".to_owned() + } + Packages::Packages(packages) => { + let first = packages + .first() + .expect("The number of packages must be at least 1"); + if packages.len() == 1 { + format!("`{}` package", first) + } else { + format!("`{}`, ... packages", first) + } + } + }; + + let mut msg = String::new(); if !suggestion.is_empty() { - anyhow::bail!( - "no {} target {} `{}`{}", + write!( + msg, + "no {} target {} `{}` in {}{}", target_desc, if is_glob { "matches pattern" } else { "named" }, target_name, - suggestion - ); + unmatched_packages(), + suggestion, + )?; + append_targets_elsewhere(&mut msg, "\n")?; } else { - let mut msg = String::new(); writeln!( msg, - "no {} target {} `{}`.", + "no {} target {} `{}` in {}.", target_desc, if is_glob { "matches pattern" } else { "named" }, target_name, + unmatched_packages() )?; - if !targets.is_empty() { + + append_targets_elsewhere(&mut msg, "")?; + if !targets.is_empty() && !need_append_targets_elsewhere { writeln!(msg, "Available {} targets:", target_desc)?; for target in targets { writeln!(msg, " {}", target.name())?; } } - anyhow::bail!(msg); } + anyhow::bail!(msg); } Ok(proposals) } + fn get_targets_from_other_packages( + &self, + filter_fn: impl Fn(&Target) -> bool, + ) -> CargoResult)>> { + let packages = Packages::All(Vec::new()).get_packages(self.ws)?; + let targets = packages + .into_iter() + .filter_map(|pkg| { + let mut targets: Vec<_> = pkg + .manifest() + .targets() + .iter() + .filter_map(|target| filter_fn(target).then(|| target.name())) + .collect(); + if targets.is_empty() { + None + } else { + targets.sort(); + Some((pkg.name().as_str(), targets)) + } + }) + .collect(); + + Ok(targets) + } + /// Returns a list of proposed targets based on command-line target selection flags. fn list_rule_targets( &self, diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 561b9b4ad..a67824d0b 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -1337,7 +1337,7 @@ fn cargo_compile_with_filename() { p.cargo("build --bin bin.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `bin.rs`. +[ERROR] no bin target named `bin.rs` in default-run packages. Available bin targets: a @@ -1348,7 +1348,7 @@ Available bin targets: p.cargo("build --bin a.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `a.rs` +[ERROR] no bin target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` @@ -1358,7 +1358,7 @@ Available bin targets: p.cargo("build --example example.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `example.rs`. +[ERROR] no example target named `example.rs` in default-run packages. Available example targets: a @@ -1369,7 +1369,7 @@ Available example targets: p.cargo("build --example a.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `a.rs` +[ERROR] no example target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` @@ -5906,7 +5906,7 @@ fn target_filters_workspace() { ws.cargo("build -v --example ex") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `ex` +[ERROR] no example target named `ex` in default-run packages [HELP] a target with a similar name exists: `ex1` @@ -5916,7 +5916,7 @@ fn target_filters_workspace() { ws.cargo("build -v --example 'ex??'") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target matches pattern `ex??` +[ERROR] no example target matches pattern `ex??` in default-run packages [HELP] a target with a similar name exists: `ex1` diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index fa3e8fc5f..1804f2c9e 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -623,7 +623,7 @@ automatically infer them to be a target, such as in subfolders. For more information on this warning you can consult https://github.com/rust-lang/cargo/issues/5330 -[ERROR] no example target named `a`. +[ERROR] no example target named `a` in default-run packages. Available example targets: do_magic @@ -655,7 +655,7 @@ fn run_example_autodiscover_2015_with_autoexamples_disabled() { p.cargo("run --example a") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `a`. +[ERROR] no example target named `a` in default-run packages. Available example targets: do_magic @@ -743,7 +743,7 @@ fn run_with_filename() { p.cargo("run --bin bin.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `bin.rs`. +[ERROR] no bin target named `bin.rs` in default-run packages. Available bin targets: a @@ -754,7 +754,7 @@ Available bin targets: p.cargo("run --bin a.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `a.rs` +[ERROR] no bin target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` @@ -764,7 +764,7 @@ Available bin targets: p.cargo("run --example example.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `example.rs`. +[ERROR] no example target named `example.rs` in default-run packages. Available example targets: a @@ -775,7 +775,7 @@ Available example targets: p.cargo("run --example a.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `a.rs` +[ERROR] no example target named `a.rs` in default-run packages [HELP] a target with a similar name exists: `a` diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index e0ec17e43..fa95a63bb 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -2447,7 +2447,7 @@ fn bad_example() { p.cargo("run --example foo") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `foo`. +[ERROR] no example target named `foo` in default-run packages. "#]]) @@ -2455,7 +2455,7 @@ fn bad_example() { p.cargo("run --bin foo") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `foo`. +[ERROR] no bin target named `foo` in default-run packages. "#]]) diff --git a/tests/testsuite/workspaces.rs b/tests/testsuite/workspaces.rs index 707b81a16..7b5a8d71e 100644 --- a/tests/testsuite/workspaces.rs +++ b/tests/testsuite/workspaces.rs @@ -2716,3 +2716,152 @@ fn nonexistence_package_together_with_workspace() { "#]]) .run(); } + +// See rust-lang/cargo#14544 +#[cargo_test] +fn print_available_targets_within_virtual_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + resolver = "3" + members = ["crate1", "crate2", "pattern1", "pattern2"] + + default-members = ["crate1"] + "#, + ) + .file("crate1/src/main.rs", "fn main(){}") + .file( + "crate1/Cargo.toml", + r#" + [package] + name = "crate1" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("crate2/src/main.rs", "fn main(){}") + .file( + "crate2/Cargo.toml", + r#" + [package] + name = "crate2" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("pattern1/src/main.rs", "fn main(){}") + .file( + "pattern1/Cargo.toml", + r#" + [package] + name = "pattern1" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("pattern2/src/main.rs", "fn main(){}") + .file( + "pattern2/Cargo.toml", + r#" + [package] + name = "pattern2" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("another/src/main.rs", "fn main(){}") + .file( + "another/Cargo.toml", + r#" + [package] + name = "another" + version = "0.1.0" + edition = "2024" + "#, + ); + + let p = p.build(); + p.cargo("run --bin") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] "--bin" takes one argument. +Available binaries: + crate1 + + +"#]]) + .run(); + + p.cargo("run -p crate1 --bin crate2") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target named `crate2` in `crate1` package + +[HELP] a target with a similar name exists: `crate1` +[HELP] Available bin in `crate2` package: + crate2 + +"#]]) + .run(); + + p.cargo("check -p crate1 -p pattern1 -p pattern2 --bin crate2") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target named `crate2` in `crate1`, ... packages + +[HELP] a target with a similar name exists: `crate1` +[HELP] Available bin in `crate2` package: + crate2 + +"#]]) + .run(); + + p.cargo("run --bin crate2") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target named `crate2` in default-run packages + +[HELP] a target with a similar name exists: `crate1` +[HELP] Available bin in `crate2` package: + crate2 + +"#]]) + .run(); + + p.cargo("check --bin pattern*") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target matches pattern `pattern*` in default-run packages. +[HELP] Available bin in `pattern1` package: + pattern1 +[HELP] Available bin in `pattern2` package: + pattern2 + +"#]]) + .run(); + + // This another branch that none of similar name exists, and print available targets in the + // default-members. + p.change_file( + "Cargo.toml", + r#" + [workspace] + resolver = "3" + members = ["crate1", "crate2", "another"] + + default-members = ["another"] + "#, + ); + + p.cargo("run --bin crate2") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target named `crate2` in default-run packages. +[HELP] Available bin in `crate2` package: + crate2 + +"#]]) + .run(); +}