diff --git a/src/bin/cargo/commands/run.rs b/src/bin/cargo/commands/run.rs index ee74d5ff9e4..503566bca2e 100644 --- a/src/bin/cargo/commands/run.rs +++ b/src/bin/cargo/commands/run.rs @@ -1,7 +1,8 @@ use crate::command_prelude::*; use crate::util::restricted_names::is_glob_pattern; -use cargo::core::Verbosity; -use cargo::ops::{self, CompileFilter, Packages}; +use cargo::core::{Verbosity, Workspace}; +use cargo::ops::{self, CompileFilter, CompileOptions, Packages}; +use cargo::util::CargoResult; use cargo_util::ProcessError; pub fn cli() -> App { @@ -52,12 +53,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { } if !args.is_present("example") && !args.is_present("bin") { - let default_runs: Vec<_> = compile_opts - .spec - .get_packages(&ws)? - .iter() - .filter_map(|pkg| pkg.manifest().default_run()) - .collect(); + let default_runs = get_default_runs(&ws, &compile_opts)?; if default_runs.len() == 1 { compile_opts.filter = CompileFilter::from_raw_arguments( false, @@ -106,3 +102,24 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { } }) } + +fn get_default_runs(ws: &Workspace<'_>, compile_opts: &CompileOptions) -> CargoResult> { + const ERROR_NOTFOUND: &'static str = "`cargo run` cannot find pkgid either in the workspace or among direct build or development dependencies"; + let matching_dependencies = cargo::ops::packages_eligible_to_run(ws, &compile_opts.spec); + match matching_dependencies { + Ok(packages) => { + if packages.is_empty() { + anyhow::bail!(ERROR_NOTFOUND); + } + + let default_runs = packages + .into_iter() + .filter_map(|pkg| pkg.manifest().default_run().map(|s| s.to_owned())) + .collect(); + Ok(default_runs) + } + Err(_) => { + anyhow::bail!(ERROR_NOTFOUND); + } + } +} diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index 69bae2c5912..a9a328e7c64 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -1,10 +1,13 @@ +use std::collections::HashSet; use std::ffi::OsString; use std::iter; use std::path::Path; use crate::core::compiler::UnitOutput; -use crate::core::{TargetKind, Workspace}; -use crate::ops; +use crate::core::dependency::DepKind; +use crate::core::resolver::Resolve; +use crate::core::{Package, PackageIdSpec, PackageSet, TargetKind, Workspace}; +use crate::ops::{self, Packages}; use crate::util::CargoResult; pub fn run( @@ -20,9 +23,10 @@ pub fn run( // We compute the `bins` here *just for diagnosis*. The actual set of // packages to be run is determined by the `ops::compile` call below. - let packages = options.spec.get_packages(ws)?; + let packages = packages_eligible_to_run(ws, &options.spec)?; + let bins: Vec<_> = packages - .into_iter() + .iter() .flat_map(|pkg| { iter::repeat(pkg).zip(pkg.manifest().targets().iter().filter(|target| { !target.is_lib() @@ -91,7 +95,7 @@ pub fn run( Ok(path) => path.to_path_buf(), Err(_) => path.to_path_buf(), }; - let pkg = bins[0].0; + let pkg = &bins[0].0; let mut process = compile.target_process(exe, unit.kind, pkg, *script_meta)?; process.args(args).cwd(config.cwd()); @@ -99,3 +103,43 @@ pub fn run( process.exec_replace() } + +pub fn packages_eligible_to_run<'a>( + ws: &Workspace<'a>, + request: &Packages, +) -> CargoResult> { + let matching_dependencies = if let ops::Packages::Packages(ref pkg_names) = request { + let specs: HashSet<_> = pkg_names + .into_iter() + .flat_map(|s| PackageIdSpec::parse(s)) + .collect(); + + let (package_set, resolver): (PackageSet<'a>, Resolve) = ops::resolve_ws(ws)?; + + // Restrict all direct dependencies only to build and development ones. + // Cargo wouldn't be able to run anything after installation, so + // normal dependencies are out. + let direct_dependencies: Vec<_> = ws + .members() + .flat_map(|pkg| resolver.deps(pkg.package_id())) + .filter(|(_, manifest_deps)| { + manifest_deps.into_iter().any(|dep| match dep.kind() { + DepKind::Development | DepKind::Build => true, + DepKind::Normal => false, + }) + }) + .collect(); + + specs.into_iter().filter_map(|pkgidspec| + // Either a workspace match… + ws.members().find(|pkg| pkgidspec.matches(pkg.package_id())) + .or_else(|| { // …or a direct dependency as fallback + let maybe_dep = direct_dependencies.iter().find(|(dep_pkgid, _)| pkgidspec.matches(*dep_pkgid)); + maybe_dep.map(|(dep_pkgid, _)| package_set.get_one(*dep_pkgid).unwrap()) + })).cloned().collect() + } else { + request.get_packages(ws)?.into_iter().cloned().collect() + }; + + Ok(matching_dependencies) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 50143978f22..9d2d58982d7 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -16,7 +16,7 @@ pub use self::cargo_output_metadata::{output_metadata, ExportInfo, OutputMetadat pub use self::cargo_package::{package, package_one, PackageOpts}; pub use self::cargo_pkgid::pkgid; pub use self::cargo_read_manifest::{read_package, read_packages}; -pub use self::cargo_run::run; +pub use self::cargo_run::{packages_eligible_to_run, run}; pub use self::cargo_test::{run_benches, run_tests, TestOptions}; pub use self::cargo_uninstall::uninstall; pub use self::fix::{fix, fix_maybe_exec_rustc, FixOptions}; diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index 5775e5b8e00..46c41bfdbf1 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -1,5 +1,6 @@ //! Tests for the `cargo run` command. +use cargo_test_support::registry::Package; use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, project, Project}; use cargo_util::paths::dylib_path_envvar; @@ -1141,8 +1142,10 @@ fn run_multiple_packages() { [workspace] - [dependencies] - d1 = { path = "d1" } + [build-dependencies] + b1 = { path = "b1" } + + [dev-dependencies] d2 = { path = "d2" } d3 = { path = "../d3" } # outside of the workspace @@ -1151,13 +1154,13 @@ fn run_multiple_packages() { "#, ) .file("foo/src/foo.rs", "fn main() { println!(\"foo\"); }") - .file("foo/d1/Cargo.toml", &basic_bin_manifest("d1")) - .file("foo/d1/src/lib.rs", "") - .file("foo/d1/src/main.rs", "fn main() { println!(\"d1\"); }") + .file("foo/b1/Cargo.toml", &basic_bin_manifest("b1")) + .file("foo/b1/src/lib.rs", "") + .file("foo/b1/src/main.rs", "fn main() { println!(\"b1\"); }") .file("foo/d2/Cargo.toml", &basic_bin_manifest("d2")) .file("foo/d2/src/main.rs", "fn main() { println!(\"d2\"); }") .file("d3/Cargo.toml", &basic_bin_manifest("d3")) - .file("d3/src/main.rs", "fn main() { println!(\"d2\"); }") + .file("d3/src/main.rs", "fn main() { println!(\"d3\"); }") .build(); let cargo = || { @@ -1166,7 +1169,7 @@ fn run_multiple_packages() { process_builder }; - cargo().arg("-p").arg("d1").with_stdout("d1").run(); + cargo().arg("-p").arg("b1").with_stdout("b1").run(); cargo() .arg("-p") @@ -1178,16 +1181,11 @@ fn run_multiple_packages() { cargo().with_stdout("foo").run(); - cargo().arg("-p").arg("d1").arg("-p").arg("d2") + cargo().arg("-p").arg("b1").arg("-p").arg("d2") .with_status(1) .with_stderr_contains("error: The argument '--package ' was provided more than once, but cannot be used multiple times").run(); - cargo() - .arg("-p") - .arg("d3") - .with_status(101) - .with_stderr_contains("[ERROR] package(s) `d3` not found in workspace [..]") - .run(); + cargo().arg("-p").arg("d3").with_stdout("d3").run(); cargo() .arg("-p") @@ -1199,6 +1197,129 @@ fn run_multiple_packages() { .run(); } +#[cargo_test] +fn run_dependency_package() { + for i in 1..=3 { + Package::new(&format!("bdep{}", i), "0.1.1") + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "bdep{}" + version = "0.1.1" + authors = ["wycats@example.com"] + + [[bin]] + name = "bdep{}" + "#, + i, i + ), + ) + .file( + "src/main.rs", + &format!("fn main() {{ println!(\"bdep{} 0.1.1\"); }}", i), + ) + .publish(); + } + + Package::new("bdep1", "0.1.0") // older version + .file( + "Cargo.toml", + r#" + [package] + name = "bdep1" + version = "0.1.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "bdep1" + "#, + ) + .file("src/main.rs", "fn main() { println!(\"bdep1 0.1.0\"); }") + .publish(); + + Package::new("libdep", "0.1.0") // library (not runnable) + .file( + "Cargo.toml", + r#" + [package] + name = "libdep" + version = "0.1.0" + authors = ["wycats@example.com"] + + [lib] + "#, + ) + .file("src/lib.rs", "pub fn f() {}") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + + [build-dependencies] + bdep1 = "0.1" + libdep = "0.1" + + [dev-dependencies] + bdep2 = "0.1.1" + + [dependencies] + bdep3 = "0" + "#, + ) + .file("src/main.rs", "fn main() { println!(\"foo\"); }") + .build(); + + let testcases = [ + ("bdep1", "bdep1 0.1.1"), + ("bdep1:0.1.1", "bdep1 0.1.1"), + ( + "https://github.com/rust-lang/crates.io-index#bdep1", + "bdep1 0.1.1", + ), + ("bdep2", "bdep2 0.1.1"), + ]; + for (pkgid, expected_output) in testcases { + p.cargo("run") + .arg("-p") + .arg(pkgid) + .with_stdout(expected_output) + .run(); + } + + p.cargo("run") + .arg("-p") + .arg("libdep") + .with_status(101) + .with_stderr_contains("[ERROR] a bin target must be available for `cargo run`") + .run(); + + let invalid_pkgids = [ + "bdep1:0.1.0", + "https://github.com/rust-lang/crates.io-index#bdep1:0.1.0", + "bdep1:0.2.0", + "https://github.com/rust-lang/crates.io-index#bdep1:0.2.0", + "bdep3", + ]; + for pkgid in invalid_pkgids { + p.cargo("run") + .arg("-p") + .arg(pkgid) + .with_status(101) + .with_stderr_contains( + "[ERROR] `cargo run` cannot find pkgid either in the workspace or among direct build or development dependencies", + ) + .run(); + } +} + #[cargo_test] fn explicit_bin_with_args() { let p = project()