Add --name and --example to cargo run

This lets us compile and run examples using `cargo run --example NAME`.
Selecting the other binary targets is now done using the `--name` flag.

`cargo run` falls back to the old behaviour (running the only bin target
in the project, failing if there are more) in neither `--name` nor
`--example` are present.

Closes #538
This commit is contained in:
Tomas Sedovic 2014-10-27 22:49:20 +01:00
parent 947a62b7db
commit f2770b31be
3 changed files with 167 additions and 10 deletions

View file

@ -2,11 +2,14 @@ use std::io::process::ExitStatus;
use cargo::ops;
use cargo::core::{MultiShell};
use cargo::util::{CliResult, CliError};
use cargo::core::manifest::{BinTarget, ExampleTarget};
use cargo::util::{CliResult, CliError, human};
use cargo::util::important_paths::{find_root_manifest_for_cwd};
#[deriving(Decodable)]
struct Options {
flag_name: Option<String>,
flag_example: Option<String>,
flag_jobs: Option<uint>,
flag_features: Vec<String>,
flag_no_default_features: bool,
@ -25,6 +28,8 @@ Usage:
Options:
-h, --help Print this message
--name NAME Name of the bin target to run
--example NAME Name of the example target to run
-j N, --jobs N The number of jobs to run in parallel
--release Build artifacts in release mode, with optimizations
--features FEATURES Space-separated list of features to also build
@ -33,6 +38,11 @@ Options:
--manifest-path PATH Path to the manifest to execute
-v, --verbose Use verbose output
If neither `--name` or `--example` are given, then if the project only has one
bin target it will be run. Otherwise `--name` specifies the bin target to run,
and `--example` specifies the example target to run. At most one of `--name` or
`--example` can be provided.
All of the trailing arguments are passed as to the binary to run.
";
@ -40,8 +50,16 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
shell.set_verbose(options.flag_verbose);
let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path));
let env = if options.flag_example.is_some() {
"test"
} else if options.flag_release {
"release"
} else {
"compile"
};
let mut compile_opts = ops::CompileOptions {
env: if options.flag_release { "release" } else { "compile" },
env: env,
shell: shell,
jobs: options.flag_jobs,
target: options.flag_target.as_ref().map(|t| t.as_slice()),
@ -51,7 +69,18 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
spec: None,
};
let err = try!(ops::run(&root, &mut compile_opts,
let (target_kind, name) = match (options.flag_name, options.flag_example) {
(Some(bin), None) => (BinTarget, Some(bin)),
(None, Some(example)) => (ExampleTarget, Some(example)),
(None, None) => (BinTarget, None),
(Some(_), Some(_)) => return Err(CliError::from_boxed(
human("specify either `--name` or `--example`, not both"), 1)),
};
let err = try!(ops::run(&root,
target_kind,
name,
&mut compile_opts,
options.arg_args.as_slice()).map_err(|err| {
CliError::from_boxed(err, 101)
}));
@ -65,4 +94,3 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
}
}
}

View file

@ -2,10 +2,13 @@ use std::os;
use ops;
use util::{CargoResult, human, process, ProcessError, Require};
use core::manifest::{TargetKind, LibTarget, BinTarget, ExampleTarget};
use core::source::Source;
use sources::PathSource;
pub fn run(manifest_path: &Path,
target_kind: TargetKind,
name: Option<String>,
options: &mut ops::CompileOptions,
args: &[String]) -> CargoResult<Option<ProcessError>> {
let mut src = try!(PathSource::for_path(&manifest_path.dir_path()));
@ -13,14 +16,21 @@ pub fn run(manifest_path: &Path,
let root = try!(src.get_root_package());
let env = options.env;
let mut bins = root.get_manifest().get_targets().iter().filter(|a| {
a.is_bin() && a.get_profile().get_env() == env
let matches_kind = match target_kind {
BinTarget => a.is_bin(),
ExampleTarget => a.is_example(),
LibTarget(_) => false,
};
let matches_name = name.as_ref().map_or(true, |n| n.as_slice() == a.get_name());
matches_kind && matches_name && a.get_profile().get_env() == env
});
let bin = try!(bins.next().require(|| {
human("a bin target must be available for `cargo run`")
}));
match bins.next() {
Some(..) => return Err(human("`cargo run` requires that a project only \
have one executable")),
Some(..) => return Err(
human("`cargo run` requires that a project only have one executable. \
Use the `--name` option to specify which one to run")),
None => {}
}
@ -28,7 +38,7 @@ pub fn run(manifest_path: &Path,
let dst = manifest_path.dir_path().join("target");
let dst = match options.target {
Some(target) => dst.join(target),
None => dst,
None => if bin.is_example() { dst.join("examples") } else { dst },
};
let exe = match bin.get_profile().get_dest() {
Some(s) => dst.join(s).join(bin.get_name()),

View file

@ -1,6 +1,6 @@
use std::path;
use support::{project, execs, path2url};
use support::{project, cargo_dir, execs, path2url};
use support::{COMPILING, RUNNING};
use hamcrest::{assert_that, existing_file};
@ -98,7 +98,126 @@ test!(too_many_bins {
assert_that(p.cargo_process("run"),
execs().with_status(101)
.with_stderr("`cargo run` requires that a project only \
have one executable\n"));
have one executable. Use the `--name` option \
to specify which one to run\n"));
})
test!(specify_name {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/lib.rs", "")
.file("src/bin/a.rs", r#"
extern crate foo;
fn main() { println!("hello a.rs"); }
"#)
.file("src/bin/b.rs", r#"
extern crate foo;
fn main() { println!("hello b.rs"); }
"#);
assert_that(p.cargo_process("run").arg("--name").arg("a"),
execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
{running} `target{sep}a`
hello a.rs
",
compiling = COMPILING,
running = RUNNING,
dir = path2url(p.root()),
sep = path::SEP).as_slice()));
assert_that(p.process(cargo_dir().join("cargo")).arg("run").arg("--name").arg("b"),
execs().with_status(0).with_stdout(format!("\
{running} `target{sep}b`
hello b.rs
",
running = RUNNING,
sep = path::SEP).as_slice()));
})
test!(run_example {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/lib.rs", "")
.file("examples/a.rs", r#"
fn main() { println!("example"); }
"#)
.file("src/bin/a.rs", r#"
fn main() { println!("bin"); }
"#);
assert_that(p.cargo_process("run").arg("--example").arg("a"),
execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
{running} `target{sep}examples{sep}a`
example
",
compiling = COMPILING,
running = RUNNING,
dir = path2url(p.root()),
sep = path::SEP).as_slice()));
})
test!(either_name_or_example {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/bin/a.rs", r#"
fn main() { println!("hello a.rs"); }
"#)
.file("examples/b.rs", r#"
fn main() { println!("hello b.rs"); }
"#);
assert_that(p.cargo_process("run").arg("--name").arg("a").arg("--example").arg("b"),
execs().with_status(1)
.with_stderr("specify either `--name` or `--example`, \
not both"));
})
test!(one_bin_multiple_examples {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/lib.rs", "")
.file("src/bin/main.rs", r#"
fn main() { println!("hello main.rs"); }
"#)
.file("examples/a.rs", r#"
fn main() { println!("hello a.rs"); }
"#)
.file("examples/b.rs", r#"
fn main() { println!("hello b.rs"); }
"#);
assert_that(p.cargo_process("run"),
execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
{running} `target{sep}main`
hello main.rs
",
compiling = COMPILING,
running = RUNNING,
dir = path2url(p.root()),
sep = path::SEP).as_slice()));
})
test!(run_dylib_dep {