diff --git a/src/cargo/ops/cargo_compile/unit_generator.rs b/src/cargo/ops/cargo_compile/unit_generator.rs index 2075cd8794c..588581457b2 100644 --- a/src/cargo/ops/cargo_compile/unit_generator.rs +++ b/src/cargo/ops/cargo_compile/unit_generator.rs @@ -259,24 +259,27 @@ impl<'a> UnitGenerator<'a, '_> { }; let proposals = self.filter_targets(filter, true, mode); if proposals.is_empty() { - let targets = self - .packages - .iter() - .flat_map(|pkg| { - pkg.targets() - .iter() - .filter(|target| is_expected_kind(target)) - }) - .collect::>(); - let suggestion = closest_msg(target_name, targets.iter(), |t| t.name(), "target"); + let mut targets = std::collections::BTreeMap::new(); + for (pkg, target) in self.packages.iter().flat_map(|pkg| { + pkg.targets() + .iter() + .filter(|target| is_expected_kind(target)) + .map(move |t| (pkg, t)) + }) { + targets + .entry(target.name()) + .or_insert_with(Vec::new) + .push((pkg, target)); + } + + let suggestion = closest_msg(target_name, targets.keys(), |t| t, "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 append_targets_elsewhere = |msg: &mut String| { let mut available_msg = Vec::new(); - for (package, targets) in targets_elsewhere { + for (package, targets) in &targets_elsewhere { if !targets.is_empty() { available_msg.push(format!( - "help: Available {target_desc} in `{package}` package:" + "help: available {target_desc} in `{package}` package:" )); for target in targets { available_msg.push(format!(" {target}")); @@ -284,12 +287,12 @@ impl<'a> UnitGenerator<'a, '_> { } } if !available_msg.is_empty() { - write!(msg, "{prefix}{}", available_msg.join("\n"))?; + write!(msg, "\n{}", available_msg.join("\n"))?; } CargoResult::Ok(()) }; - let unmatched_packages = || match self.spec { + let unmatched_packages = match self.spec { Packages::Default | Packages::OptOut(_) | Packages::All(_) => { "default-run packages".to_owned() } @@ -305,33 +308,25 @@ impl<'a> UnitGenerator<'a, '_> { } }; + let named = if is_glob { "matches pattern" } else { "named" }; + let mut msg = String::new(); - if !suggestion.is_empty() { - write!( - msg, - "no {} target {} `{}` in {}{}", - target_desc, - if is_glob { "matches pattern" } else { "named" }, - target_name, - unmatched_packages(), - suggestion, - )?; - append_targets_elsewhere(&mut msg, "\n")?; - } else { - writeln!( - msg, - "no {} target {} `{}` in {}.", - target_desc, - if is_glob { "matches pattern" } else { "named" }, - target_name, - unmatched_packages() - )?; - - 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())?; + write!( + msg, + "no {target_desc} target {named} `{target_name}` in {unmatched_packages}{suggestion}", + )?; + if !targets_elsewhere.is_empty() { + append_targets_elsewhere(&mut msg)?; + } else if suggestion.is_empty() && !targets.is_empty() { + write!(msg, "\nhelp: available {} targets:", target_desc)?; + for (target_name, pkgs) in targets { + if pkgs.len() == 1 { + write!(msg, "\n {target_name}")?; + } else { + for (pkg, _) in pkgs { + let pkg_name = pkg.name(); + write!(msg, "\n {target_name} in package {pkg_name}")?; + } } } } diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index 46be0291058..25d13e923a3 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -1,4 +1,5 @@ use std::ffi::OsString; +use std::fmt::Write as _; use std::iter; use std::path::Path; @@ -69,10 +70,20 @@ pub fn run( names.join(", ") ) } else { - anyhow::bail!( - "`cargo run` can run at most one executable, but \ + let mut message = "`cargo run` can run at most one executable, but \ multiple were specified" - ) + .to_owned(); + write!(&mut message, "\nhelp: available targets:")?; + for (pkg, bin) in &bins { + write!( + &mut message, + "\n {} `{}` in package `{}`", + bin.kind().description(), + bin.name(), + pkg.name() + )?; + } + anyhow::bail!(message) } } diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index a67824d0b44..2bdf2b26d18 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -1337,11 +1337,10 @@ 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` in default-run packages. -Available bin targets: +[ERROR] no bin target named `bin.rs` in default-run packages +[HELP] available bin targets: a - "#]]) .run(); @@ -1358,11 +1357,10 @@ Available bin targets: p.cargo("build --example example.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `example.rs` in default-run packages. -Available example targets: +[ERROR] no example target named `example.rs` in default-run packages +[HELP] available example targets: a - "#]]) .run(); diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index 1804f2c9eae..271b8e7acd1 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -623,11 +623,10 @@ 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` in default-run packages. -Available example targets: +[ERROR] no example target named `a` in default-run packages +[HELP] available example targets: do_magic - "#]]) .run(); } @@ -655,11 +654,10 @@ 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` in default-run packages. -Available example targets: +[ERROR] no example target named `a` in default-run packages +[HELP] available example targets: do_magic - "#]]) .run(); } @@ -743,11 +741,10 @@ 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` in default-run packages. -Available bin targets: +[ERROR] no bin target named `bin.rs` in default-run packages +[HELP] available bin targets: a - "#]]) .run(); @@ -764,11 +761,10 @@ Available bin targets: p.cargo("run --example example.rs") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `example.rs` in default-run packages. -Available example targets: +[ERROR] no example target named `example.rs` in default-run packages +[HELP] available example targets: a - "#]]) .run(); @@ -783,6 +779,235 @@ Available example targets: .run(); } +#[cargo_test] +fn ambiguous_bin_name() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + resolver = "3" + members = ["crate1", "crate2", "crate3", "crate4"] + "#, + ) + .file("crate1/src/bin/ambiguous.rs", "fn main(){}") + .file( + "crate1/Cargo.toml", + r#" + [package] + name = "crate1" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("crate2/src/bin/ambiguous.rs", "fn main(){}") + .file( + "crate2/Cargo.toml", + r#" + [package] + name = "crate2" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("crate3/src/bin/ambiguous.rs", "fn main(){}") + .file( + "crate3/Cargo.toml", + r#" + [package] + name = "crate3" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("crate4/src/bin/ambiguous.rs", "fn main(){}") + .file( + "crate4/Cargo.toml", + r#" + [package] + name = "crate4" + version = "0.1.0" + edition = "2024" + "#, + ); + let p = p.build(); + + p.cargo("run --bin ambiguous") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] `cargo run` can run at most one executable, but multiple were specified +[HELP] available targets: + bin `ambiguous` in package `crate1` + bin `ambiguous` in package `crate2` + bin `ambiguous` in package `crate3` + bin `ambiguous` in package `crate4` + +"#]]) + .run(); + + p.cargo("run --bin crate1/ambiguous") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] no bin target named `crate1/ambiguous` in default-run packages +[HELP] available bin targets: + ambiguous in package crate1 + ambiguous in package crate2 + ambiguous in package crate3 + ambiguous in package crate4 + +"#]]) + .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(); +} + #[cargo_test] fn either_name_or_example() { let p = project() @@ -794,6 +1019,9 @@ fn either_name_or_example() { .with_status(101) .with_stderr_data(str![[r#" [ERROR] `cargo run` can run at most one executable, but multiple were specified +[HELP] available targets: + bin `a` in package `foo` + example `b` in package `foo` "#]]) .run(); diff --git a/tests/testsuite/test.rs b/tests/testsuite/test.rs index 7f69efb4d9c..6d9941f0149 100644 --- a/tests/testsuite/test.rs +++ b/tests/testsuite/test.rs @@ -2447,16 +2447,14 @@ fn bad_example() { p.cargo("run --example foo") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no example target named `foo` in default-run packages. - +[ERROR] no example target named `foo` in default-run packages "#]]) .run(); p.cargo("run --bin foo") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] no bin target named `foo` in default-run packages. - +[ERROR] no bin target named `foo` in default-run packages "#]]) .run(); diff --git a/tests/testsuite/workspaces.rs b/tests/testsuite/workspaces.rs index 7b5a8d71e52..707b81a167c 100644 --- a/tests/testsuite/workspaces.rs +++ b/tests/testsuite/workspaces.rs @@ -2716,152 +2716,3 @@ 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(); -}