Add a cargo run command

This currently only supports executing the `src/main.rs` convention, no other.

Close #149
This commit is contained in:
Alex Crichton 2014-07-13 11:47:37 -07:00
parent ec0895b460
commit a64b9073f4
10 changed files with 194 additions and 10 deletions

View File

@ -22,6 +22,7 @@ BINS = cargo \
cargo-verify-project \
cargo-git-checkout \
cargo-test \
cargo-run
SRC = $(shell find src -name '*.rs' -not -path 'src/bin*')

View File

@ -56,7 +56,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
"compile"
};
let opts = CompileOptions {
let mut opts = CompileOptions {
update: options.update_remotes,
env: env,
shell: shell,
@ -64,7 +64,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
target: options.target.as_ref().map(|t| t.as_slice()),
};
ops::compile(&root, opts).map(|_| None).map_err(|err| {
ops::compile(&root, &mut opts).map(|_| None).map_err(|err| {
CliError::from_boxed(err, 101)
})
}

69
src/bin/cargo-run.rs Normal file
View File

@ -0,0 +1,69 @@
#![crate_name = "cargo-run"]
#![feature(phase)]
#[phase(plugin, link)]
extern crate cargo;
extern crate serialize;
#[phase(plugin, link)]
extern crate hammer;
use std::os;
use std::io::process::ExitStatus;
use cargo::ops;
use cargo::{execute_main_without_stdin};
use cargo::core::{MultiShell};
use cargo::util::{CliResult, CliError};
use cargo::util::important_paths::find_project_manifest;
#[deriving(PartialEq,Clone,Decodable)]
struct Options {
manifest_path: Option<String>,
jobs: Option<uint>,
update: bool,
rest: Vec<String>,
}
hammer_config!(Options "Run the package's main executable", |c| {
c.short("jobs", 'j').short("update", 'u')
})
fn main() {
execute_main_without_stdin(execute);
}
fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
let root = match options.manifest_path {
Some(path) => Path::new(path),
None => try!(find_project_manifest(&os::getcwd(), "Cargo.toml")
.map_err(|_| {
CliError::new("Could not find Cargo.toml in this \
directory or any parent directory",
102)
}))
};
let mut compile_opts = ops::CompileOptions {
update: options.update,
env: "compile",
shell: shell,
jobs: options.jobs,
target: None,
};
let err = try!(ops::run(&root, &mut compile_opts,
options.rest.as_slice()).map_err(|err| {
CliError::from_boxed(err, 101)
}));
match err {
None => Ok(None),
Some(err) => {
Err(match err.exit {
Some(ExitStatus(i)) => CliError::from_boxed(box err, i as uint),
_ => CliError::from_boxed(box err, 101),
})
}
}
}

View File

@ -44,7 +44,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
}))
};
let compile_opts = ops::CompileOptions {
let mut compile_opts = ops::CompileOptions {
update: options.update,
env: "test",
shell: shell,
@ -52,7 +52,8 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
target: None,
};
let test_executables = try!(ops::compile(&root, compile_opts).map_err(|err| {
let test_executables = try!(ops::compile(&root,
&mut compile_opts).map_err(|err| {
CliError::from_boxed(err, 101)
}));

View File

@ -51,8 +51,10 @@ fn execute() {
println!("Commands:");
println!(" build # compile the current project");
println!(" test # run the tests");
println!(" clean # remove the target directory\n");
println!(" clean # remove the target directory");
println!(" run # build and execute src/main.rs");
println!("");
let (_, options) = hammer::usage::<GlobalFlags>(false);
println!("Options (for all commands):\n\n{}", options);

View File

@ -38,8 +38,9 @@ pub struct CompileOptions<'a> {
pub target: Option<&'a str>,
}
pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec<String>> {
let CompileOptions { update, env, shell, jobs, target } = options;
pub fn compile(manifest_path: &Path,
options: &mut CompileOptions) -> CargoResult<Vec<String>> {
let CompileOptions { update, env, ref mut shell, jobs, target } = *options;
let target = target.map(|s| s.to_string());
log!(4, "compile; manifest-path={}", manifest_path.display());
@ -60,7 +61,7 @@ pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec
let source_ids = package.get_source_ids();
let (packages, resolve) = {
let mut config = try!(Config::new(shell, update, jobs, target.clone()));
let mut config = try!(Config::new(*shell, update, jobs, target.clone()));
let mut registry =
try!(PackageRegistry::new(source_ids, override_ids, &mut config));
@ -83,7 +84,7 @@ pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec
target.get_profile().get_env() == env
}).collect::<Vec<&Target>>();
let mut config = try!(Config::new(shell, update, jobs, target));
let mut config = try!(Config::new(*shell, update, jobs, target));
try!(ops::compile_targets(env.as_slice(), targets.as_slice(), &package,
&PackageSet::new(packages.as_slice()), &resolve, &mut config));

View File

@ -0,0 +1,23 @@
use std::os;
use ops;
use util::{CargoResult, human, process, ProcessError};
pub fn run(manifest_path: &Path,
options: &mut ops::CompileOptions,
args: &[String]) -> CargoResult<Option<ProcessError>> {
if !manifest_path.dir_path().join("src").join("main.rs").exists() {
return Err(human("`src/main.rs` must be present for `cargo run`"))
}
try!(ops::compile(manifest_path, options));
let exe = manifest_path.dir_path().join("target/main");
let exe = match exe.path_relative_from(&os::getcwd()) {
Some(path) => path,
None => exe,
};
let process = process(exe).args(args);
try!(options.shell.status("Running", process.to_string()));
Ok(process.exec().err())
}

View File

@ -2,8 +2,10 @@ pub use self::cargo_clean::clean;
pub use self::cargo_compile::{compile, CompileOptions};
pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
pub use self::cargo_rustc::compile_targets;
pub use self::cargo_run::run;
mod cargo_clean;
mod cargo_compile;
mod cargo_read_manifest;
mod cargo_rustc;
mod cargo_run;

84
tests/test_cargo_run.rs Normal file
View File

@ -0,0 +1,84 @@
use std::path;
use support::{project, execs};
use support::{COMPILING, RUNNING};
use hamcrest::{assert_that, existing_file};
fn setup() {
}
test!(simple {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/main.rs", r#"
fn main() { println!("hello"); }
"#);
assert_that(p.cargo_process("cargo-run"),
execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 (file:{dir})
{running} `target{sep}main`
hello
",
compiling = COMPILING,
running = RUNNING,
dir = p.root().display(),
sep = path::SEP).as_slice()));
assert_that(&p.bin("main"), existing_file());
})
test!(simple_with_args {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/main.rs", r#"
fn main() {
assert_eq!(std::os::args().get(1).as_slice(), "hello");
assert_eq!(std::os::args().get(2).as_slice(), "world");
}
"#);
assert_that(p.cargo_process("cargo-run").arg("hello").arg("world"),
execs().with_status(0));
})
test!(exit_code {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/main.rs", r#"
fn main() { std::os::set_exit_status(2); }
"#);
assert_that(p.cargo_process("cargo-run"),
execs().with_status(2));
})
test!(no_main_file {
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
"#)
.file("src/lib.rs", "");
assert_that(p.cargo_process("cargo-run"),
execs().with_status(101)
.with_stderr("`src/main.rs` must be present for \
`cargo run`\n"));
})

View File

@ -27,3 +27,4 @@ mod test_cargo_compile_path_deps;
mod test_cargo_test;
mod test_shell;
mod test_cargo_cross_compile;
mod test_cargo_run;