Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bc577dc

Browse files
authoredMar 13, 2025··
fix(run): Disambiguate bins from different packages that share a name (#15298)
### What does this PR try to resolve? This builds on the work done in #15199 to improve target selection errors to also disambiguate when the same binary name is used in multiple packages. This also makes the errors from #15199 more consistent with the rustc style guide and reduces code duplication. Fixes #13312 ### How should we test and review this PR? This is a first pass and does not do the full `--package foo --bin bar` syntax. I wanted to focus on the basic functionality before we iterated on it, e.g. investigating how well we can predict the CLI flags, how much noise they might add, etc. ### Additional information
2 parents 611b7c4 + d29a7cb commit bc577dc

File tree

6 files changed

+297
-216
lines changed

6 files changed

+297
-216
lines changed
 

‎src/cargo/ops/cargo_compile/unit_generator.rs

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -259,37 +259,40 @@ impl<'a> UnitGenerator<'a, '_> {
259259
};
260260
let proposals = self.filter_targets(filter, true, mode);
261261
if proposals.is_empty() {
262-
let targets = self
263-
.packages
264-
.iter()
265-
.flat_map(|pkg| {
266-
pkg.targets()
267-
.iter()
268-
.filter(|target| is_expected_kind(target))
269-
})
270-
.collect::<Vec<_>>();
271-
let suggestion = closest_msg(target_name, targets.iter(), |t| t.name(), "target");
262+
let mut targets = std::collections::BTreeMap::new();
263+
for (pkg, target) in self.packages.iter().flat_map(|pkg| {
264+
pkg.targets()
265+
.iter()
266+
.filter(|target| is_expected_kind(target))
267+
.map(move |t| (pkg, t))
268+
}) {
269+
targets
270+
.entry(target.name())
271+
.or_insert_with(Vec::new)
272+
.push((pkg, target));
273+
}
274+
275+
let suggestion = closest_msg(target_name, targets.keys(), |t| t, "target");
272276
let targets_elsewhere = self.get_targets_from_other_packages(filter)?;
273-
let need_append_targets_elsewhere = !targets_elsewhere.is_empty();
274-
let append_targets_elsewhere = |msg: &mut String, prefix: &str| {
277+
let append_targets_elsewhere = |msg: &mut String| {
275278
let mut available_msg = Vec::new();
276-
for (package, targets) in targets_elsewhere {
279+
for (package, targets) in &targets_elsewhere {
277280
if !targets.is_empty() {
278281
available_msg.push(format!(
279-
"help: Available {target_desc} in `{package}` package:"
282+
"help: available {target_desc} in `{package}` package:"
280283
));
281284
for target in targets {
282285
available_msg.push(format!(" {target}"));
283286
}
284287
}
285288
}
286289
if !available_msg.is_empty() {
287-
write!(msg, "{prefix}{}", available_msg.join("\n"))?;
290+
write!(msg, "\n{}", available_msg.join("\n"))?;
288291
}
289292
CargoResult::Ok(())
290293
};
291294

292-
let unmatched_packages = || match self.spec {
295+
let unmatched_packages = match self.spec {
293296
Packages::Default | Packages::OptOut(_) | Packages::All(_) => {
294297
"default-run packages".to_owned()
295298
}
@@ -305,33 +308,25 @@ impl<'a> UnitGenerator<'a, '_> {
305308
}
306309
};
307310

311+
let named = if is_glob { "matches pattern" } else { "named" };
312+
308313
let mut msg = String::new();
309-
if !suggestion.is_empty() {
310-
write!(
311-
msg,
312-
"no {} target {} `{}` in {}{}",
313-
target_desc,
314-
if is_glob { "matches pattern" } else { "named" },
315-
target_name,
316-
unmatched_packages(),
317-
suggestion,
318-
)?;
319-
append_targets_elsewhere(&mut msg, "\n")?;
320-
} else {
321-
writeln!(
322-
msg,
323-
"no {} target {} `{}` in {}.",
324-
target_desc,
325-
if is_glob { "matches pattern" } else { "named" },
326-
target_name,
327-
unmatched_packages()
328-
)?;
329-
330-
append_targets_elsewhere(&mut msg, "")?;
331-
if !targets.is_empty() && !need_append_targets_elsewhere {
332-
writeln!(msg, "Available {} targets:", target_desc)?;
333-
for target in targets {
334-
writeln!(msg, " {}", target.name())?;
314+
write!(
315+
msg,
316+
"no {target_desc} target {named} `{target_name}` in {unmatched_packages}{suggestion}",
317+
)?;
318+
if !targets_elsewhere.is_empty() {
319+
append_targets_elsewhere(&mut msg)?;
320+
} else if suggestion.is_empty() && !targets.is_empty() {
321+
write!(msg, "\nhelp: available {} targets:", target_desc)?;
322+
for (target_name, pkgs) in targets {
323+
if pkgs.len() == 1 {
324+
write!(msg, "\n {target_name}")?;
325+
} else {
326+
for (pkg, _) in pkgs {
327+
let pkg_name = pkg.name();
328+
write!(msg, "\n {target_name} in package {pkg_name}")?;
329+
}
335330
}
336331
}
337332
}

‎src/cargo/ops/cargo_run.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::ffi::OsString;
2+
use std::fmt::Write as _;
23
use std::iter;
34
use std::path::Path;
45

@@ -69,10 +70,20 @@ pub fn run(
6970
names.join(", ")
7071
)
7172
} else {
72-
anyhow::bail!(
73-
"`cargo run` can run at most one executable, but \
73+
let mut message = "`cargo run` can run at most one executable, but \
7474
multiple were specified"
75-
)
75+
.to_owned();
76+
write!(&mut message, "\nhelp: available targets:")?;
77+
for (pkg, bin) in &bins {
78+
write!(
79+
&mut message,
80+
"\n {} `{}` in package `{}`",
81+
bin.kind().description(),
82+
bin.name(),
83+
pkg.name()
84+
)?;
85+
}
86+
anyhow::bail!(message)
7687
}
7788
}
7889

‎tests/testsuite/build.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,11 +1337,10 @@ fn cargo_compile_with_filename() {
13371337
p.cargo("build --bin bin.rs")
13381338
.with_status(101)
13391339
.with_stderr_data(str![[r#"
1340-
[ERROR] no bin target named `bin.rs` in default-run packages.
1341-
Available bin targets:
1340+
[ERROR] no bin target named `bin.rs` in default-run packages
1341+
[HELP] available bin targets:
13421342
a
13431343
1344-
13451344
"#]])
13461345
.run();
13471346

@@ -1358,11 +1357,10 @@ Available bin targets:
13581357
p.cargo("build --example example.rs")
13591358
.with_status(101)
13601359
.with_stderr_data(str![[r#"
1361-
[ERROR] no example target named `example.rs` in default-run packages.
1362-
Available example targets:
1360+
[ERROR] no example target named `example.rs` in default-run packages
1361+
[HELP] available example targets:
13631362
a
13641363
1365-
13661364
"#]])
13671365
.run();
13681366

‎tests/testsuite/run.rs

Lines changed: 240 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -623,11 +623,10 @@ automatically infer them to be a target, such as in subfolders.
623623
624624
For more information on this warning you can consult
625625
https://github.com/rust-lang/cargo/issues/5330
626-
[ERROR] no example target named `a` in default-run packages.
627-
Available example targets:
626+
[ERROR] no example target named `a` in default-run packages
627+
[HELP] available example targets:
628628
do_magic
629629
630-
631630
"#]])
632631
.run();
633632
}
@@ -655,11 +654,10 @@ fn run_example_autodiscover_2015_with_autoexamples_disabled() {
655654
p.cargo("run --example a")
656655
.with_status(101)
657656
.with_stderr_data(str![[r#"
658-
[ERROR] no example target named `a` in default-run packages.
659-
Available example targets:
657+
[ERROR] no example target named `a` in default-run packages
658+
[HELP] available example targets:
660659
do_magic
661660
662-
663661
"#]])
664662
.run();
665663
}
@@ -743,11 +741,10 @@ fn run_with_filename() {
743741
p.cargo("run --bin bin.rs")
744742
.with_status(101)
745743
.with_stderr_data(str![[r#"
746-
[ERROR] no bin target named `bin.rs` in default-run packages.
747-
Available bin targets:
744+
[ERROR] no bin target named `bin.rs` in default-run packages
745+
[HELP] available bin targets:
748746
a
749747
750-
751748
"#]])
752749
.run();
753750

@@ -764,11 +761,10 @@ Available bin targets:
764761
p.cargo("run --example example.rs")
765762
.with_status(101)
766763
.with_stderr_data(str![[r#"
767-
[ERROR] no example target named `example.rs` in default-run packages.
768-
Available example targets:
764+
[ERROR] no example target named `example.rs` in default-run packages
765+
[HELP] available example targets:
769766
a
770767
771-
772768
"#]])
773769
.run();
774770

@@ -783,6 +779,235 @@ Available example targets:
783779
.run();
784780
}
785781

782+
#[cargo_test]
783+
fn ambiguous_bin_name() {
784+
let p = project()
785+
.file(
786+
"Cargo.toml",
787+
r#"
788+
[workspace]
789+
resolver = "3"
790+
members = ["crate1", "crate2", "crate3", "crate4"]
791+
"#,
792+
)
793+
.file("crate1/src/bin/ambiguous.rs", "fn main(){}")
794+
.file(
795+
"crate1/Cargo.toml",
796+
r#"
797+
[package]
798+
name = "crate1"
799+
version = "0.1.0"
800+
edition = "2024"
801+
"#,
802+
)
803+
.file("crate2/src/bin/ambiguous.rs", "fn main(){}")
804+
.file(
805+
"crate2/Cargo.toml",
806+
r#"
807+
[package]
808+
name = "crate2"
809+
version = "0.1.0"
810+
edition = "2024"
811+
"#,
812+
)
813+
.file("crate3/src/bin/ambiguous.rs", "fn main(){}")
814+
.file(
815+
"crate3/Cargo.toml",
816+
r#"
817+
[package]
818+
name = "crate3"
819+
version = "0.1.0"
820+
edition = "2024"
821+
"#,
822+
)
823+
.file("crate4/src/bin/ambiguous.rs", "fn main(){}")
824+
.file(
825+
"crate4/Cargo.toml",
826+
r#"
827+
[package]
828+
name = "crate4"
829+
version = "0.1.0"
830+
edition = "2024"
831+
"#,
832+
);
833+
let p = p.build();
834+
835+
p.cargo("run --bin ambiguous")
836+
.with_status(101)
837+
.with_stderr_data(str![[r#"
838+
[ERROR] `cargo run` can run at most one executable, but multiple were specified
839+
[HELP] available targets:
840+
bin `ambiguous` in package `crate1`
841+
bin `ambiguous` in package `crate2`
842+
bin `ambiguous` in package `crate3`
843+
bin `ambiguous` in package `crate4`
844+
845+
"#]])
846+
.run();
847+
848+
p.cargo("run --bin crate1/ambiguous")
849+
.with_status(101)
850+
.with_stderr_data(str![[r#"
851+
[ERROR] no bin target named `crate1/ambiguous` in default-run packages
852+
[HELP] available bin targets:
853+
ambiguous in package crate1
854+
ambiguous in package crate2
855+
ambiguous in package crate3
856+
ambiguous in package crate4
857+
858+
"#]])
859+
.run();
860+
}
861+
862+
// See rust-lang/cargo#14544
863+
#[cargo_test]
864+
fn print_available_targets_within_virtual_workspace() {
865+
let p = project()
866+
.file(
867+
"Cargo.toml",
868+
r#"
869+
[workspace]
870+
resolver = "3"
871+
members = ["crate1", "crate2", "pattern1", "pattern2"]
872+
873+
default-members = ["crate1"]
874+
"#,
875+
)
876+
.file("crate1/src/main.rs", "fn main(){}")
877+
.file(
878+
"crate1/Cargo.toml",
879+
r#"
880+
[package]
881+
name = "crate1"
882+
version = "0.1.0"
883+
edition = "2024"
884+
"#,
885+
)
886+
.file("crate2/src/main.rs", "fn main(){}")
887+
.file(
888+
"crate2/Cargo.toml",
889+
r#"
890+
[package]
891+
name = "crate2"
892+
version = "0.1.0"
893+
edition = "2024"
894+
"#,
895+
)
896+
.file("pattern1/src/main.rs", "fn main(){}")
897+
.file(
898+
"pattern1/Cargo.toml",
899+
r#"
900+
[package]
901+
name = "pattern1"
902+
version = "0.1.0"
903+
edition = "2024"
904+
"#,
905+
)
906+
.file("pattern2/src/main.rs", "fn main(){}")
907+
.file(
908+
"pattern2/Cargo.toml",
909+
r#"
910+
[package]
911+
name = "pattern2"
912+
version = "0.1.0"
913+
edition = "2024"
914+
"#,
915+
)
916+
.file("another/src/main.rs", "fn main(){}")
917+
.file(
918+
"another/Cargo.toml",
919+
r#"
920+
[package]
921+
name = "another"
922+
version = "0.1.0"
923+
edition = "2024"
924+
"#,
925+
);
926+
927+
let p = p.build();
928+
p.cargo("run --bin")
929+
.with_status(101)
930+
.with_stderr_data(str![[r#"
931+
[ERROR] "--bin" takes one argument.
932+
Available binaries:
933+
crate1
934+
935+
936+
"#]])
937+
.run();
938+
939+
p.cargo("run -p crate1 --bin crate2")
940+
.with_status(101)
941+
.with_stderr_data(str![[r#"
942+
[ERROR] no bin target named `crate2` in `crate1` package
943+
944+
[HELP] a target with a similar name exists: `crate1`
945+
[HELP] available bin in `crate2` package:
946+
crate2
947+
948+
"#]])
949+
.run();
950+
951+
p.cargo("check -p crate1 -p pattern1 -p pattern2 --bin crate2")
952+
.with_status(101)
953+
.with_stderr_data(str![[r#"
954+
[ERROR] no bin target named `crate2` in `crate1`, ... packages
955+
956+
[HELP] a target with a similar name exists: `crate1`
957+
[HELP] available bin in `crate2` package:
958+
crate2
959+
960+
"#]])
961+
.run();
962+
963+
p.cargo("run --bin crate2")
964+
.with_status(101)
965+
.with_stderr_data(str![[r#"
966+
[ERROR] no bin target named `crate2` in default-run packages
967+
968+
[HELP] a target with a similar name exists: `crate1`
969+
[HELP] available bin in `crate2` package:
970+
crate2
971+
972+
"#]])
973+
.run();
974+
975+
p.cargo("check --bin pattern*")
976+
.with_status(101)
977+
.with_stderr_data(str![[r#"
978+
[ERROR] no bin target matches pattern `pattern*` in default-run packages
979+
[HELP] available bin in `pattern1` package:
980+
pattern1
981+
[HELP] available bin in `pattern2` package:
982+
pattern2
983+
984+
"#]])
985+
.run();
986+
987+
// This another branch that none of similar name exists, and print available targets in the
988+
// default-members.
989+
p.change_file(
990+
"Cargo.toml",
991+
r#"
992+
[workspace]
993+
resolver = "3"
994+
members = ["crate1", "crate2", "another"]
995+
996+
default-members = ["another"]
997+
"#,
998+
);
999+
1000+
p.cargo("run --bin crate2")
1001+
.with_status(101)
1002+
.with_stderr_data(str![[r#"
1003+
[ERROR] no bin target named `crate2` in default-run packages
1004+
[HELP] available bin in `crate2` package:
1005+
crate2
1006+
1007+
"#]])
1008+
.run();
1009+
}
1010+
7861011
#[cargo_test]
7871012
fn either_name_or_example() {
7881013
let p = project()
@@ -794,6 +1019,9 @@ fn either_name_or_example() {
7941019
.with_status(101)
7951020
.with_stderr_data(str![[r#"
7961021
[ERROR] `cargo run` can run at most one executable, but multiple were specified
1022+
[HELP] available targets:
1023+
bin `a` in package `foo`
1024+
example `b` in package `foo`
7971025
7981026
"#]])
7991027
.run();

‎tests/testsuite/test.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,16 +2447,14 @@ fn bad_example() {
24472447
p.cargo("run --example foo")
24482448
.with_status(101)
24492449
.with_stderr_data(str![[r#"
2450-
[ERROR] no example target named `foo` in default-run packages.
2451-
2450+
[ERROR] no example target named `foo` in default-run packages
24522451
24532452
"#]])
24542453
.run();
24552454
p.cargo("run --bin foo")
24562455
.with_status(101)
24572456
.with_stderr_data(str![[r#"
2458-
[ERROR] no bin target named `foo` in default-run packages.
2459-
2457+
[ERROR] no bin target named `foo` in default-run packages
24602458
24612459
"#]])
24622460
.run();

‎tests/testsuite/workspaces.rs

Lines changed: 0 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,152 +2716,3 @@ fn nonexistence_package_together_with_workspace() {
27162716
"#]])
27172717
.run();
27182718
}
2719-
2720-
// See rust-lang/cargo#14544
2721-
#[cargo_test]
2722-
fn print_available_targets_within_virtual_workspace() {
2723-
let p = project()
2724-
.file(
2725-
"Cargo.toml",
2726-
r#"
2727-
[workspace]
2728-
resolver = "3"
2729-
members = ["crate1", "crate2", "pattern1", "pattern2"]
2730-
2731-
default-members = ["crate1"]
2732-
"#,
2733-
)
2734-
.file("crate1/src/main.rs", "fn main(){}")
2735-
.file(
2736-
"crate1/Cargo.toml",
2737-
r#"
2738-
[package]
2739-
name = "crate1"
2740-
version = "0.1.0"
2741-
edition = "2024"
2742-
"#,
2743-
)
2744-
.file("crate2/src/main.rs", "fn main(){}")
2745-
.file(
2746-
"crate2/Cargo.toml",
2747-
r#"
2748-
[package]
2749-
name = "crate2"
2750-
version = "0.1.0"
2751-
edition = "2024"
2752-
"#,
2753-
)
2754-
.file("pattern1/src/main.rs", "fn main(){}")
2755-
.file(
2756-
"pattern1/Cargo.toml",
2757-
r#"
2758-
[package]
2759-
name = "pattern1"
2760-
version = "0.1.0"
2761-
edition = "2024"
2762-
"#,
2763-
)
2764-
.file("pattern2/src/main.rs", "fn main(){}")
2765-
.file(
2766-
"pattern2/Cargo.toml",
2767-
r#"
2768-
[package]
2769-
name = "pattern2"
2770-
version = "0.1.0"
2771-
edition = "2024"
2772-
"#,
2773-
)
2774-
.file("another/src/main.rs", "fn main(){}")
2775-
.file(
2776-
"another/Cargo.toml",
2777-
r#"
2778-
[package]
2779-
name = "another"
2780-
version = "0.1.0"
2781-
edition = "2024"
2782-
"#,
2783-
);
2784-
2785-
let p = p.build();
2786-
p.cargo("run --bin")
2787-
.with_status(101)
2788-
.with_stderr_data(str![[r#"
2789-
[ERROR] "--bin" takes one argument.
2790-
Available binaries:
2791-
crate1
2792-
2793-
2794-
"#]])
2795-
.run();
2796-
2797-
p.cargo("run -p crate1 --bin crate2")
2798-
.with_status(101)
2799-
.with_stderr_data(str![[r#"
2800-
[ERROR] no bin target named `crate2` in `crate1` package
2801-
2802-
[HELP] a target with a similar name exists: `crate1`
2803-
[HELP] Available bin in `crate2` package:
2804-
crate2
2805-
2806-
"#]])
2807-
.run();
2808-
2809-
p.cargo("check -p crate1 -p pattern1 -p pattern2 --bin crate2")
2810-
.with_status(101)
2811-
.with_stderr_data(str![[r#"
2812-
[ERROR] no bin target named `crate2` in `crate1`, ... packages
2813-
2814-
[HELP] a target with a similar name exists: `crate1`
2815-
[HELP] Available bin in `crate2` package:
2816-
crate2
2817-
2818-
"#]])
2819-
.run();
2820-
2821-
p.cargo("run --bin crate2")
2822-
.with_status(101)
2823-
.with_stderr_data(str![[r#"
2824-
[ERROR] no bin target named `crate2` in default-run packages
2825-
2826-
[HELP] a target with a similar name exists: `crate1`
2827-
[HELP] Available bin in `crate2` package:
2828-
crate2
2829-
2830-
"#]])
2831-
.run();
2832-
2833-
p.cargo("check --bin pattern*")
2834-
.with_status(101)
2835-
.with_stderr_data(str![[r#"
2836-
[ERROR] no bin target matches pattern `pattern*` in default-run packages.
2837-
[HELP] Available bin in `pattern1` package:
2838-
pattern1
2839-
[HELP] Available bin in `pattern2` package:
2840-
pattern2
2841-
2842-
"#]])
2843-
.run();
2844-
2845-
// This another branch that none of similar name exists, and print available targets in the
2846-
// default-members.
2847-
p.change_file(
2848-
"Cargo.toml",
2849-
r#"
2850-
[workspace]
2851-
resolver = "3"
2852-
members = ["crate1", "crate2", "another"]
2853-
2854-
default-members = ["another"]
2855-
"#,
2856-
);
2857-
2858-
p.cargo("run --bin crate2")
2859-
.with_status(101)
2860-
.with_stderr_data(str![[r#"
2861-
[ERROR] no bin target named `crate2` in default-run packages.
2862-
[HELP] Available bin in `crate2` package:
2863-
crate2
2864-
2865-
"#]])
2866-
.run();
2867-
}

0 commit comments

Comments
 (0)
Please sign in to comment.