diff --git a/src/bin/cargo/commands/bench.rs b/src/bin/cargo/commands/bench.rs index 087510e2555..aa44eb0d817 100644 --- a/src/bin/cargo/commands/bench.rs +++ b/src/bin/cargo/commands/bench.rs @@ -72,7 +72,8 @@ Compilation can be customized with the `bench` profile in the manifest. pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; - let mut compile_opts = args.compile_options(config, CompileMode::Bench)?; + let mut compile_opts = args.compile_options(config, CompileMode::Bench, Some(&ws))?; + compile_opts.build_config.release = true; let ops = TestOptions { diff --git a/src/bin/cargo/commands/build.rs b/src/bin/cargo/commands/build.rs index fa2605070b0..3938a8e5630 100644 --- a/src/bin/cargo/commands/build.rs +++ b/src/bin/cargo/commands/build.rs @@ -48,7 +48,8 @@ the --release flag will use the `release` profile instead. pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; - let mut compile_opts = args.compile_options(config, CompileMode::Build)?; + let mut compile_opts = args.compile_options(config, CompileMode::Build, Some(&ws))?; + compile_opts.export_dir = args.value_of_path("out-dir", config); if compile_opts.export_dir.is_some() && !config.cli_unstable().unstable_options { Err(failure::format_err!( diff --git a/src/bin/cargo/commands/check.rs b/src/bin/cargo/commands/check.rs index 33d92796670..b3dfeb15d81 100644 --- a/src/bin/cargo/commands/check.rs +++ b/src/bin/cargo/commands/check.rs @@ -68,7 +68,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { } }; let mode = CompileMode::Check { test }; - let compile_opts = args.compile_options(config, mode)?; + let compile_opts = args.compile_options(config, mode, Some(&ws))?; + ops::compile(&ws, &compile_opts)?; Ok(()) } diff --git a/src/bin/cargo/commands/doc.rs b/src/bin/cargo/commands/doc.rs index b32c793ffd1..abe5754942b 100644 --- a/src/bin/cargo/commands/doc.rs +++ b/src/bin/cargo/commands/doc.rs @@ -50,7 +50,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let mode = CompileMode::Doc { deps: !args.is_present("no-deps"), }; - let mut compile_opts = args.compile_options(config, mode)?; + let mut compile_opts = args.compile_options(config, mode, Some(&ws))?; compile_opts.local_rustdoc_args = if args.is_present("document-private-items") { Some(vec!["--document-private-items".to_string()]) } else { diff --git a/src/bin/cargo/commands/fix.rs b/src/bin/cargo/commands/fix.rs index 9b58c22b04c..bc654297439 100644 --- a/src/bin/cargo/commands/fix.rs +++ b/src/bin/cargo/commands/fix.rs @@ -122,7 +122,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { // Unlike other commands default `cargo fix` to all targets to fix as much // code as we can. - let mut opts = args.compile_options(config, mode)?; + let mut opts = args.compile_options(config, mode, Some(&ws))?; + if let CompileFilter::Default { .. } = opts.filter { opts.filter = CompileFilter::Only { all_targets: true, diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index f0dc4d1439d..8702fd99a39 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -78,7 +78,9 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let registry = args.registry(config)?; config.reload_rooted_at_cargo_home()?; - let mut compile_opts = args.compile_options(config, CompileMode::Build)?; + + let workspace = args.workspace(config).ok(); + let mut compile_opts = args.compile_options(config, CompileMode::Build, workspace.as_ref())?; compile_opts.build_config.release = !args.is_present("debug"); diff --git a/src/bin/cargo/commands/run.rs b/src/bin/cargo/commands/run.rs index 20a3e89d29b..fc8401d5615 100644 --- a/src/bin/cargo/commands/run.rs +++ b/src/bin/cargo/commands/run.rs @@ -39,7 +39,8 @@ run. If you're passing arguments to both Cargo and the binary, the ones after pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; - let mut compile_opts = args.compile_options(config, CompileMode::Build)?; + let mut compile_opts = args.compile_options(config, CompileMode::Build, Some(&ws))?; + if !args.is_present("example") && !args.is_present("bin") { let default_runs: Vec<_> = compile_opts .spec diff --git a/src/bin/cargo/commands/rustc.rs b/src/bin/cargo/commands/rustc.rs index cd959cc559d..b0400b556b3 100644 --- a/src/bin/cargo/commands/rustc.rs +++ b/src/bin/cargo/commands/rustc.rs @@ -62,7 +62,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { return Err(CliError::new(err, 101)); } }; - let mut compile_opts = args.compile_options_for_single_package(config, mode)?; + let mut compile_opts = args.compile_options_for_single_package(config, mode, Some(&ws))?; let target_args = values(args, "args"); compile_opts.target_rustc_args = if target_args.is_empty() { None diff --git a/src/bin/cargo/commands/rustdoc.rs b/src/bin/cargo/commands/rustdoc.rs index 14bde6fe298..f016d286330 100644 --- a/src/bin/cargo/commands/rustdoc.rs +++ b/src/bin/cargo/commands/rustdoc.rs @@ -51,7 +51,7 @@ the `cargo help pkgid` command. pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; let mut compile_opts = - args.compile_options_for_single_package(config, CompileMode::Doc { deps: false })?; + args.compile_options_for_single_package(config, CompileMode::Doc { deps: false }, Some(&ws))?; let target_args = values(args, "args"); compile_opts.target_rustdoc_args = if target_args.is_empty() { None diff --git a/src/bin/cargo/commands/test.rs b/src/bin/cargo/commands/test.rs index 0f1ef69ede2..63ac24c5311 100644 --- a/src/bin/cargo/commands/test.rs +++ b/src/bin/cargo/commands/test.rs @@ -92,7 +92,7 @@ To get the list of all options available for the test binaries use this: pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; - let mut compile_opts = args.compile_options(config, CompileMode::Test)?; + let mut compile_opts = args.compile_options(config, CompileMode::Test, Some(&ws))?; let doc = args.is_present("doc"); if doc { diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index 4ae067585ea..1ad216142a9 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -7,6 +7,10 @@ use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionCon use crate::sources::CRATES_IO_REGISTRY; use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::{paths, validate_package_name}; +use crate::util::{ + print_available_benches, print_available_binaries, print_available_examples, + print_available_tests, +}; use crate::CargoResult; use clap::{self, SubCommand}; @@ -60,18 +64,18 @@ pub trait AppExt: Sized { all: &'static str, ) -> Self { self.arg_targets_lib_bin(lib, bin, bins) - ._arg(multi_opt("example", "NAME", example)) + ._arg(optional_multi_opt("example", "NAME", example)) ._arg(opt("examples", examples)) - ._arg(multi_opt("test", "NAME", test)) + ._arg(optional_multi_opt("test", "NAME", test)) ._arg(opt("tests", tests)) - ._arg(multi_opt("bench", "NAME", bench)) + ._arg(optional_multi_opt("bench", "NAME", bench)) ._arg(opt("benches", benches)) ._arg(opt("all-targets", all)) } fn arg_targets_lib_bin(self, lib: &'static str, bin: &'static str, bins: &'static str) -> Self { self._arg(opt("lib", lib)) - ._arg(multi_opt("bin", "NAME", bin)) + ._arg(optional_multi_opt("bin", "NAME", bin)) ._arg(opt("bins", bins)) } @@ -82,15 +86,15 @@ pub trait AppExt: Sized { example: &'static str, examples: &'static str, ) -> Self { - self._arg(multi_opt("bin", "NAME", bin)) + self._arg(optional_multi_opt("bin", "NAME", bin)) ._arg(opt("bins", bins)) - ._arg(multi_opt("example", "NAME", example)) + ._arg(optional_multi_opt("example", "NAME", example)) ._arg(opt("examples", examples)) } fn arg_targets_bin_example(self, bin: &'static str, example: &'static str) -> Self { - self._arg(multi_opt("bin", "NAME", bin)) - ._arg(multi_opt("example", "NAME", example)) + self._arg(optional_multi_opt("bin", "NAME", bin)) + ._arg(optional_multi_opt("example", "NAME", example)) } fn arg_features(self) -> Self { @@ -193,6 +197,18 @@ pub fn opt(name: &'static str, help: &'static str) -> Arg<'static, 'static> { Arg::with_name(name).long(name).help(help) } +pub fn optional_multi_opt( + name: &'static str, + value_name: &'static str, + help: &'static str, +) -> Arg<'static, 'static> { + opt(name, help) + .value_name(value_name) + .multiple(true) + .min_values(0) + .number_of_values(1) +} + pub fn multi_opt( name: &'static str, value_name: &'static str, @@ -272,6 +288,7 @@ pub trait ArgMatchesExt { &self, config: &'a Config, mode: CompileMode, + workspace: Option<&Workspace<'a>>, ) -> CargoResult> { let spec = Packages::from_flags( self._is_present("all"), @@ -328,6 +345,11 @@ pub trait ArgMatchesExt { local_rustdoc_args: None, export_dir: None, }; + + if let Some(ws) = workspace { + self.check_optional_opts(ws, &opts)?; + } + Ok(opts) } @@ -335,8 +357,9 @@ pub trait ArgMatchesExt { &self, config: &'a Config, mode: CompileMode, + workspace: Option<&Workspace<'a>>, ) -> CargoResult> { - let mut compile_opts = self.compile_options(config, mode)?; + let mut compile_opts = self.compile_options(config, mode, workspace)?; compile_opts.spec = Packages::Packages(self._values_of("package")); Ok(compile_opts) } @@ -413,6 +436,34 @@ about this warning."; Ok(index) } + fn check_optional_opts( + &self, + workspace: &Workspace<'_>, + compile_opts: &CompileOptions<'_>, + ) -> CargoResult<()> { + if self.is_present_with_zero_values("example") { + print_available_examples(&workspace, &compile_opts)?; + } + + if self.is_present_with_zero_values("bin") { + print_available_binaries(&workspace, &compile_opts)?; + } + + if self.is_present_with_zero_values("bench") { + print_available_benches(&workspace, &compile_opts)?; + } + + if self.is_present_with_zero_values("test") { + print_available_tests(&workspace, &compile_opts)?; + } + + Ok(()) + } + + fn is_present_with_zero_values(&self, name: &str) -> bool { + self._is_present(name) && self._value_of(name).is_none() + } + fn _value_of(&self, name: &str) -> Option<&str>; fn _values_of(&self, name: &str) -> Vec; diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index 2860133445e..ef2cffba9a1 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -22,6 +22,10 @@ pub use self::sha256::Sha256; pub use self::to_semver::ToSemver; pub use self::to_url::ToUrl; pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo}; +pub use self::workspace::{ + print_available_benches, print_available_binaries, print_available_examples, + print_available_tests, +}; mod cfg; pub mod command_prelude; @@ -49,6 +53,7 @@ pub mod to_semver; pub mod to_url; pub mod toml; mod vcs; +mod workspace; pub fn elapsed(duration: Duration) -> String { let secs = duration.as_secs(); diff --git a/src/cargo/util/workspace.rs b/src/cargo/util/workspace.rs new file mode 100644 index 00000000000..d25ee156053 --- /dev/null +++ b/src/cargo/util/workspace.rs @@ -0,0 +1,75 @@ +use crate::core::{Target, Workspace}; +use crate::ops::CompileOptions; +use crate::util::CargoResult; + +use std::fmt::Write; + +fn get_available_targets<'a>( + filter_fn: fn(&Target) -> bool, + ws: &'a Workspace<'_>, + options: &'a CompileOptions<'_>, +) -> CargoResult> { + let packages = options.spec.get_packages(ws)?; + + let mut targets: Vec<_> = packages + .into_iter() + .flat_map(|pkg| { + pkg.manifest() + .targets() + .into_iter() + .filter(|target| filter_fn(target)) + }) + .collect(); + + targets.sort(); + + Ok(targets) +} + +fn print_available( + filter_fn: fn(&Target) -> bool, + ws: &Workspace<'_>, + options: &CompileOptions<'_>, + option_name: &str, + plural_name: &str, +) -> CargoResult<()> { + let targets = get_available_targets(filter_fn, ws, options)?; + + let mut output = String::new(); + writeln!(output, "\"{}\" takes one argument.", option_name)?; + + if targets.is_empty() { + writeln!(output, "No {} available.", plural_name)?; + } else { + writeln!(output, "Available {}:", plural_name)?; + for target in targets { + writeln!(output, " {}", target.name())?; + } + } + Err(failure::err_msg(output))? +} + +pub fn print_available_examples( + ws: &Workspace<'_>, + options: &CompileOptions<'_>, +) -> CargoResult<()> { + print_available(Target::is_example, ws, options, "--example", "examples") +} + +pub fn print_available_binaries( + ws: &Workspace<'_>, + options: &CompileOptions<'_>, +) -> CargoResult<()> { + print_available(Target::is_bin, ws, options, "--bin", "binaries") +} + +pub fn print_available_benches( + ws: &Workspace<'_>, + options: &CompileOptions<'_>, +) -> CargoResult<()> { + print_available(Target::is_bench, ws, options, "--bench", "benches") +} + +pub fn print_available_tests(ws: &Workspace<'_>, options: &CompileOptions<'_>) -> CargoResult<()> { + print_available(Target::is_test, ws, options, "--test", "tests") +} diff --git a/tests/testsuite/list_targets.rs b/tests/testsuite/list_targets.rs new file mode 100644 index 00000000000..0166bc130b1 --- /dev/null +++ b/tests/testsuite/list_targets.rs @@ -0,0 +1,189 @@ +use crate::support::project; + +const EXAMPLE: u8 = 0x1; +const BIN: u8 = 0x2; +const TEST: u8 = 0x4; +const BENCH: u8 = 0x8; + +fn list_targets_test(command: &str, targets: u8) { + let full_project = project() + .file("examples/a.rs", "fn main() { }") + .file("examples/b.rs", "fn main() { }") + .file("benches/bench1.rs", "") + .file("benches/bench2.rs", "") + .file("tests/test1.rs", "") + .file("tests/test2.rs", "") + .file("src/main.rs", "fn main() { }") + .build(); + + if targets & EXAMPLE != 0 { + full_project + .cargo(&format!("{} --example", command)) + .with_stderr( + "\ +error: \"--example\" takes one argument. +Available examples: + a + b + +", + ) + .with_status(101) + .run(); + } + + if targets & BIN != 0 { + full_project + .cargo(&format!("{} --bin", command)) + .with_stderr( + "\ +error: \"--bin\" takes one argument. +Available binaries: + foo + +", + ) + .with_status(101) + .run(); + } + + if targets & BENCH != 0 { + full_project + .cargo(&format!("{} --bench", command)) + .with_stderr( + "\ +error: \"--bench\" takes one argument. +Available benches: + bench1 + bench2 + +", + ) + .with_status(101) + .run(); + } + + if targets & TEST != 0 { + full_project + .cargo(&format!("{} --test", command)) + .with_stderr( + "\ +error: \"--test\" takes one argument. +Available tests: + test1 + test2 + +", + ) + .with_status(101) + .run(); + } + + let empty_project = project().file("src/lib.rs", "").build(); + + if targets & EXAMPLE != 0 { + empty_project + .cargo(&format!("{} --example", command)) + .with_stderr( + "\ +error: \"--example\" takes one argument. +No examples available. + +", + ) + .with_status(101) + .run(); + } + + if targets & BIN != 0 { + empty_project + .cargo(&format!("{} --bin", command)) + .with_stderr( + "\ +error: \"--bin\" takes one argument. +No binaries available. + +", + ) + .with_status(101) + .run(); + } + + if targets & BENCH != 0 { + empty_project + .cargo(&format!("{} --bench", command)) + .with_stderr( + "\ +error: \"--bench\" takes one argument. +No benches available. + +", + ) + .with_status(101) + .run(); + } + + if targets & TEST != 0 { + empty_project + .cargo(&format!("{} --test", command)) + .with_stderr( + "\ +error: \"--test\" takes one argument. +No tests available. + +", + ) + .with_status(101) + .run(); + } +} + +#[test] +fn build_list_targets() { + list_targets_test("build", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn check_list_targets() { + list_targets_test("check", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn doc_list_targets() { + list_targets_test("doc", BIN); +} + +#[test] +fn fix_list_targets() { + list_targets_test("fix", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn run_list_targets() { + list_targets_test("run", EXAMPLE | BIN); +} + +#[test] +fn test_list_targets() { + list_targets_test("test", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn bench_list_targets() { + list_targets_test("bench", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn install_list_targets() { + list_targets_test("install", EXAMPLE | BIN); +} + +#[test] +fn rustdoc_list_targets() { + list_targets_test("rustdoc", EXAMPLE | BIN | TEST | BENCH); +} + +#[test] +fn rustc_list_targets() { + list_targets_test("rustc", EXAMPLE | BIN | TEST | BENCH); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index d170646999a..38979992315 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -43,6 +43,7 @@ mod git; mod init; mod install; mod jobserver; +mod list_targets; mod local_registry; mod lockfile_compat; mod login;