Skip to content

Make commands in dependencies available to run #9833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions src/bin/cargo/commands/run.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -106,3 +102,24 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
}
})
}

fn get_default_runs(ws: &Workspace<'_>, compile_opts: &CompileOptions) -> CargoResult<Vec<String>> {
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);
}
}
}
54 changes: 49 additions & 5 deletions src/cargo/ops/cargo_run.rs
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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()
Expand Down Expand Up @@ -91,11 +95,51 @@ 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());

config.shell().status("Running", process.to_string())?;

process.exec_replace()
}

pub fn packages_eligible_to_run<'a>(
ws: &Workspace<'a>,
request: &Packages,
) -> CargoResult<Vec<Package>> {
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)
}
2 changes: 1 addition & 1 deletion src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
149 changes: 135 additions & 14 deletions tests/testsuite/run.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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

Expand All @@ -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 = || {
Expand All @@ -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")
Expand All @@ -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 <SPEC>' 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")
Expand All @@ -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 = ["[email protected]"]

[[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 = ["[email protected]"]

[[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 = ["[email protected]"]

[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()
Expand Down