mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
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
This commit is contained in:
commit
bc577dc6c4
@ -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::<Vec<_>>();
|
||||
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 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()
|
||||
)?;
|
||||
let named = if is_glob { "matches pattern" } else { "named" };
|
||||
|
||||
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())?;
|
||||
let mut msg = String::new();
|
||||
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}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user