diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 1256787f4f8..26faf702e31 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -662,8 +662,11 @@ fn scrape_target_config(config: &Config, triple: &str) None => return Ok(ret), }; for (lib_name, value) in table { - if lib_name == "ar" || lib_name == "linker" || lib_name == "rustflags" { - continue + match lib_name.as_str() { + "ar" | "linker" | "runner" | "rustflags" => { + continue + }, + _ => {} } let mut output = BuildOutput { diff --git a/src/cargo/ops/cargo_rustc/compilation.rs b/src/cargo/ops/cargo_rustc/compilation.rs index b34850a7978..be3e16a0437 100644 --- a/src/cargo/ops/cargo_rustc/compilation.rs +++ b/src/cargo/ops/cargo_rustc/compilation.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use semver::Version; use core::{PackageId, Package, Target, TargetKind}; -use util::{self, CargoResult, Config, ProcessBuilder, process, join_paths}; +use util::{self, CargoResult, Config, LazyCell, ProcessBuilder, process, join_paths}; /// A structure returning the result of a compilation. pub struct Compilation<'cfg> { @@ -53,6 +53,8 @@ pub struct Compilation<'cfg> { pub target: String, config: &'cfg Config, + + target_runner: LazyCell)>>, } impl<'cfg> Compilation<'cfg> { @@ -72,6 +74,7 @@ impl<'cfg> Compilation<'cfg> { cfgs: HashMap::new(), config: config, target: String::new(), + target_runner: LazyCell::new(), } } @@ -91,10 +94,25 @@ impl<'cfg> Compilation<'cfg> { self.fill_env(process(cmd), pkg, true) } + fn target_runner(&self) -> CargoResult<&Option<(PathBuf, Vec)>> { + self.target_runner.get_or_try_init(|| { + let key = format!("target.{}.runner", self.target); + Ok(self.config.get_path_and_args(&key)?.map(|v| v.val)) + }) + } + /// See `process`. pub fn target_process>(&self, cmd: T, pkg: &Package) -> CargoResult { - self.fill_env(process(cmd), pkg, false) + let builder = if let &Some((ref runner, ref args)) = self.target_runner()? { + let mut builder = process(runner); + builder.args(args); + builder.arg(cmd); + builder + } else { + process(cmd) + }; + self.fill_env(builder, pkg, false) } /// Prepares a new process with an appropriate environment to run against diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index aa27fa0436c..269384145b3 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -215,25 +215,41 @@ impl Config { } } + fn string_to_path(&self, value: String, definition: &Definition) -> PathBuf { + let is_path = value.contains('/') || + (cfg!(windows) && value.contains('\\')); + if is_path { + definition.root(self).join(value) + } else { + // A pathless name + PathBuf::from(value) + } + } + pub fn get_path(&self, key: &str) -> CargoResult>> { if let Some(val) = self.get_string(key)? { - let is_path = val.val.contains('/') || - (cfg!(windows) && val.val.contains('\\')); - let path = if is_path { - val.definition.root(self).join(val.val) - } else { - // A pathless name - PathBuf::from(val.val) - }; Ok(Some(Value { - val: path, - definition: val.definition, + val: self.string_to_path(val.val, &val.definition), + definition: val.definition })) } else { Ok(None) } } + pub fn get_path_and_args(&self, key: &str) + -> CargoResult)>>> { + if let Some(mut val) = self.get_list_or_split_string(key)? { + if !val.val.is_empty() { + return Ok(Some(Value { + val: (self.string_to_path(val.val.remove(0), &val.definition), val.val), + definition: val.definition + })); + } + } + Ok(None) + } + pub fn get_list(&self, key: &str) -> CargoResult>>> { match self.get(key)? { diff --git a/src/doc/config.md b/src/doc/config.md index 382b5c14529..679bc5f9bc9 100644 --- a/src/doc/config.md +++ b/src/doc/config.md @@ -57,15 +57,20 @@ email = "..." vcs = "none" # For the following sections, $triple refers to any valid target triple, not the -# literal string "$triple", and it will apply whenever that target triple is +# literal string "$triple", and it will apply whenever that target triple is # being compiled to. 'cfg(...)' refers to the Rust-like `#[cfg]` syntax for # conditional compilation. -[target.$triple] -# This is the linker which is passed to rustc (via `-C linker=`) when the `$triple` +[target.$triple] +# This is the linker which is passed to rustc (via `-C linker=`) when the `$triple` # is being compiled for. By default this flag is not passed to the compiler. -linker = ".." -# Same but for the library archiver which is passed to rustc via `-C ar=`. +linker = ".." +# Same but for the library archiver which is passed to rustc via `-C ar=`. ar = ".." +# If a runner is provided, compiled targets for the `$triple` will be executed +# by invoking the specified runner executable with actual target as first argument. +# This applies to `cargo run`, `cargo test` and `cargo bench` commands. +# By default compiled targets are executed directly. +runner = ".." # custom flags to pass to all compiler invocations that target $triple # this value overrides build.rustflags when both are present rustflags = ["..", ".."] diff --git a/tests/tool-paths.rs b/tests/tool-paths.rs index dcb9e636811..ef1a183d956 100644 --- a/tests/tool-paths.rs +++ b/tests/tool-paths.rs @@ -124,3 +124,48 @@ fn relative_tools() { [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", url = foo_url, ar = output.0, linker = output.1))) } + +#[test] +fn custom_runner() { + let target = rustc_host(); + + let foo = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + "#) + .file("src/main.rs", "fn main() {}") + .file("tests/test.rs", "") + .file("benches/bench.rs", "") + .file(".cargo/config", &format!(r#" + [target.{}] + runner = "nonexistent-runner -r" + "#, target)); + + foo.build(); + + assert_that(foo.cargo("run").args(&["--", "--param"]), + execs().with_stderr_contains(&format!("\ +[COMPILING] foo v0.0.1 ({url}) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[RUNNING] `nonexistent-runner -r target[/]debug[/]foo[EXE] --param` +", url = foo.url()))); + + assert_that(foo.cargo("test").args(&["--test", "test", "--verbose", "--", "--param"]), + execs().with_stderr_contains(&format!("\ +[COMPILING] foo v0.0.1 ({url}) +[RUNNING] `rustc [..]` +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[RUNNING] `nonexistent-runner -r [..][/]target[/]debug[/]deps[/]test-[..][EXE] --param` +", url = foo.url()))); + + assert_that(foo.cargo("bench").args(&["--bench", "bench", "--verbose", "--", "--param"]), + execs().with_stderr_contains(&format!("\ +[COMPILING] foo v0.0.1 ({url}) +[RUNNING] `rustc [..]` +[RUNNING] `rustc [..]` +[FINISHED] release [optimized] target(s) in [..] +[RUNNING] `nonexistent-runner -r [..][/]target[/]release[/]deps[/]bench-[..][EXE] --param --bench` +", url = foo.url()))); +}