Skip to content

Commit

Permalink
Auto merge of #3954 - RReverser:run-with, r=alexcrichton
Browse files Browse the repository at this point in the history
Add support for custom target-specific runners

When `target.$triple.runner` is specified, it will be used for any execution commands by cargo including `cargo run`, `cargo test` and `cargo bench`. The original file is passed to the runner executable as a first argument.

This allows to run tests when cross-comping Rust projects.

This is not a complete solution and might be extended in future for better ergonomics to support passing extra arguments to the runner itself or overriding runner from the command line, but it should already unlock major existing use cases.

Fixes #1411
Resolves #3626
  • Loading branch information
bors committed May 13, 2017
2 parents 13d92c6 + 0f1c687 commit 3fa09c2
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 19 deletions.
7 changes: 5 additions & 2 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 20 additions & 2 deletions src/cargo/ops/cargo_rustc/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -53,6 +53,8 @@ pub struct Compilation<'cfg> {
pub target: String,

config: &'cfg Config,

target_runner: LazyCell<Option<(PathBuf, Vec<String>)>>,
}

impl<'cfg> Compilation<'cfg> {
Expand All @@ -72,6 +74,7 @@ impl<'cfg> Compilation<'cfg> {
cfgs: HashMap::new(),
config: config,
target: String::new(),
target_runner: LazyCell::new(),
}
}

Expand All @@ -91,10 +94,25 @@ impl<'cfg> Compilation<'cfg> {
self.fill_env(process(cmd), pkg, true)
}

fn target_runner(&self) -> CargoResult<&Option<(PathBuf, Vec<String>)>> {
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<T: AsRef<OsStr>>(&self, cmd: T, pkg: &Package)
-> CargoResult<ProcessBuilder> {
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
Expand Down
36 changes: 26 additions & 10 deletions src/cargo/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Value<PathBuf>>> {
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<Option<Value<(PathBuf, Vec<String>)>>> {
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<Option<Value<Vec<(String, PathBuf)>>>> {
match self.get(key)? {
Expand Down
15 changes: 10 additions & 5 deletions src/doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ["..", ".."]
Expand Down
45 changes: 45 additions & 0 deletions tests/tool-paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())));
}

0 comments on commit 3fa09c2

Please sign in to comment.