diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index f4513b94b..c147a5631 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -18,12 +18,16 @@ pub struct Manifest { authors: Vec, targets: Vec, target_dir: Path, - sources: Vec + sources: Vec, + build: Option, } impl Show for Manifest { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Manifest({}, authors={}, targets={}, target_dir={})", self.summary, self.authors, self.targets, self.target_dir.display()) + write!(f, "Manifest({}, authors={}, targets={}, target_dir={}, \ + build={})", + self.summary, self.authors, self.targets, + self.target_dir.display(), self.build) } } @@ -34,7 +38,8 @@ pub struct SerializedManifest { dependencies: Vec, authors: Vec, targets: Vec, - target_dir: String + target_dir: String, + build: Option, } impl> Encodable for Manifest { @@ -45,7 +50,8 @@ impl> Encodable for Manifest { dependencies: self.summary.get_dependencies().iter().map(|d| SerializedDependency::from_dependency(d)).collect(), authors: self.authors.clone(), targets: self.targets.clone(), - target_dir: self.target_dir.display().to_str() + target_dir: self.target_dir.display().to_str(), + build: self.build.clone(), }.encode(s) } } @@ -125,13 +131,16 @@ impl Show for Target { } impl Manifest { - pub fn new(summary: &Summary, targets: &[Target], target_dir: &Path, sources: Vec) -> Manifest { + pub fn new(summary: &Summary, targets: &[Target], + target_dir: &Path, sources: Vec, + build: Option) -> Manifest { Manifest { summary: summary.clone(), authors: Vec::new(), targets: Vec::from_slice(targets), target_dir: target_dir.clone(), - sources: sources + sources: sources, + build: build, } } @@ -170,6 +179,10 @@ impl Manifest { pub fn get_source_ids<'a>(&'a self) -> &'a [SourceId] { self.sources.as_slice() } + + pub fn get_build<'a>(&'a self) -> Option<&'a str> { + self.build.as_ref().map(|s| s.as_slice()) + } } impl Target { diff --git a/src/cargo/ops/cargo_rustc.rs b/src/cargo/ops/cargo_rustc.rs index 0f6cee715..b86a83738 100644 --- a/src/cargo/ops/cargo_rustc.rs +++ b/src/cargo/ops/cargo_rustc.rs @@ -3,7 +3,7 @@ use std::io; use std::path::Path; use core::{Package,PackageSet,Target}; use util; -use util::{CargoResult, ChainError, ProcessBuilder, internal, human}; +use util::{CargoResult, ChainError, ProcessBuilder, internal, human, CargoError}; type Args = Vec; @@ -33,6 +33,11 @@ pub fn compile_packages(pkg: &Package, deps: &PackageSet) -> CargoResult<()> { fn compile_pkg(pkg: &Package, dest: &Path, deps_dir: &Path, primary: bool) -> CargoResult<()> { debug!("compile_pkg; pkg={}; targets={}", pkg, pkg.get_targets()); + match pkg.get_manifest().get_build() { + Some(cmd) => try!(compile_custom(pkg, cmd, dest, deps_dir, primary)), + None => {} + } + // compile for target in pkg.get_targets().iter() { // Only compile lib targets for dependencies @@ -48,6 +53,20 @@ fn mk_target(target: &Path) -> CargoResult<()> { io::fs::mkdir_recursive(target, io::UserRWX).chain_error(|| internal("could not create target directory")) } +fn compile_custom(pkg: &Package, cmd: &str, dest: &Path, deps_dir: &Path, + _primary: bool) -> CargoResult<()> { + // FIXME: this needs to be smarter about splitting + let mut cmd = cmd.split(' '); + let mut p = util::process(cmd.next().unwrap()) + .cwd(pkg.get_root()) + .env("OUT_DIR", Some(dest.as_str().unwrap())) + .env("DEPS_DIR", Some(dest.join(deps_dir).as_str().unwrap())); + for arg in cmd { + p = p.arg(arg); + } + p.exec_with_output().map(|_| ()).map_err(|e| e.mark_human()) +} + fn rustc(root: &Path, target: &Target, dest: &Path, deps: &Path, verbose: bool) -> CargoResult<()> { let crate_types = target.rustc_crate_types(); @@ -72,7 +91,7 @@ fn prepare_rustc(root: &Path, target: &Target, crate_type: &'static str, dest: & let mut args = Vec::new(); build_base_args(&mut args, target, crate_type, dest); - build_deps_args(&mut args, deps); + build_deps_args(&mut args, dest, deps); util::process("rustc") .cwd(root.clone()) @@ -89,9 +108,11 @@ fn build_base_args(into: &mut Args, target: &Target, crate_type: &'static str, d into.push(dest.display().to_str()); } -fn build_deps_args(dst: &mut Args, deps: &Path) { +fn build_deps_args(dst: &mut Args, deps: &Path, dest: &Path) { dst.push("-L".to_str()); dst.push(deps.display().to_str()); + dst.push("-L".to_str()); + dst.push(dest.display().to_str()); } fn topsort(deps: &PackageSet) -> CargoResult { diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 03c38753e..649336664 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -2,6 +2,7 @@ use std::io::process::{Command,ProcessOutput,ProcessExit,ExitStatus,ExitSignal}; use std::io::IoError; use std::fmt; use std::fmt::{Show, Formatter}; +use std::str; use TomlError = toml::Error; diff --git a/src/cargo/util/process_builder.rs b/src/cargo/util/process_builder.rs index 251cc9773..314095129 100644 --- a/src/cargo/util/process_builder.rs +++ b/src/cargo/util/process_builder.rs @@ -31,8 +31,13 @@ impl Show for ProcessBuilder { static PATH_SEP : &'static str = ":"; impl ProcessBuilder { - pub fn args(mut self, arguments: &[T]) -> ProcessBuilder { - self.args = arguments.iter().map(|a| a.to_str()).collect(); + pub fn arg(mut self, arg: T) -> ProcessBuilder { + self.args.push(arg.as_slice().to_str()); + self + } + + pub fn args(mut self, arguments: &[T]) -> ProcessBuilder { + self.args = arguments.iter().map(|a| a.as_slice().to_str()).collect(); self } @@ -98,14 +103,18 @@ impl ProcessBuilder { } } - fn build_command(&self) -> Command { + pub fn build_command(&self) -> Command { let mut command = Command::new(self.program.as_slice()); command.args(self.args.as_slice()).cwd(&self.cwd); command } fn debug_string(&self) -> String { - format!("{} {}", self.program, self.args.connect(" ")) + if self.args.len() == 0 { + self.program.to_str() + } else { + format!("{} {}", self.program, self.args.connect(" ")) + } } fn build_env(&self) -> Vec<(String, String)> { diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 6312dc1a5..3c6039551 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -101,7 +101,8 @@ pub struct TomlManifest { pub struct TomlProject { pub name: String, pub version: String, - pub authors: Vec + pub authors: Vec, + build: Option, } impl TomlProject { @@ -159,10 +160,13 @@ impl TomlManifest { } Ok((Manifest::new( - &Summary::new(&self.project.to_package_id(source_id.get_url()), deps.as_slice()), + &Summary::new(&self.project.to_package_id(source_id.get_url()), + deps.as_slice()), targets.as_slice(), &Path::new("target"), - sources), nested_paths)) + sources, + self.project.build.clone()), + nested_paths)) } } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 621b4481f..a976a158d 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -2,7 +2,7 @@ use std; use std::io; use std::io::fs; -use std::io::process::{ProcessOutput,ProcessExit}; +use std::io::process::{ProcessOutput}; use std::os; use std::path::{Path,BytesContainer}; use std::str; @@ -80,7 +80,7 @@ impl ProjectBuilder { .extra_path(cargo_dir()) } - pub fn file(mut self, path: B, body: S) -> ProjectBuilder { + pub fn file(mut self, path: B, body: S) -> ProjectBuilder { self.files.push(FileBuilder::new(self.root.join(path), body.as_slice())); self } @@ -202,18 +202,21 @@ impl Execs { } fn match_output(&self, actual: &ProcessOutput) -> ham::MatchResult { - self.match_status(actual.status) + self.match_status(actual) .and(self.match_stdout(actual)) .and(self.match_stderr(actual)) } - fn match_status(&self, actual: ProcessExit) -> ham::MatchResult { + fn match_status(&self, actual: &ProcessOutput) -> ham::MatchResult { match self.expect_exit_code { None => ham::success(), Some(code) => { ham::expect( - actual.matches_exit_status(code), - format!("exited with {}", actual)) + actual.status.matches_exit_status(code), + format!("exited with {}\n--- stdout\n{}\n--- stderr\n{}", + actual.status, + str::from_utf8(actual.output.as_slice()), + str::from_utf8(actual.error.as_slice()))) } } } diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index e096df9b2..adf992a6d 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -64,7 +64,15 @@ test!(cargo_compile_with_invalid_code { assert_that(p.cargo_process("cargo-compile"), execs() .with_status(101) - .with_stderr(format!("src/foo.rs:1:1: 1:8 error: expected item but found `invalid`\nsrc/foo.rs:1 invalid rust code!\n ^~~~~~~\nCould not execute process `rustc src/foo.rs --crate-type bin --out-dir {} -L {}` (status=101)", target.display(), target.join("deps").display()).as_slice())); + .with_stderr(format!("\ +src/foo.rs:1:1: 1:8 error: expected item but found `invalid` +src/foo.rs:1 invalid rust code! + ^~~~~~~ +Could not execute process \ +`rustc src/foo.rs --crate-type bin --out-dir {} -L {} -L {}` (status=101)", + target.display(), + target.display(), + target.join("deps").display()).as_slice())); }) test!(cargo_compile_with_warnings_in_the_root_package { @@ -285,3 +293,188 @@ test!(cargo_compile_with_nested_deps_longhand { }) // test!(compiling_project_with_invalid_manifest) + +test!(custom_build { + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { println!("Hello!"); } + "#); + assert_that(build.cargo_process("cargo-compile"), + execs().with_status(0)); + + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "{}" + + [[bin]] name = "foo" + "#, build.root().join("target/foo").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("cargo-compile"), + execs().with_status(0) + .with_stdout(format!("Compiling foo v0.5.0 (file:{})\n", + p.root().display())) + .with_stderr("")); +}) + +test!(custom_build_failure { + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { fail!("nope") } + "#); + assert_that(build.cargo_process("cargo-compile"), execs().with_status(0)); + + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "{}" + + [[bin]] name = "foo" + "#, build.root().join("target/foo").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("cargo-compile"), + execs().with_status(101).with_stderr(format!("\ +Could not execute process `{}` (status=101) +--- stderr +task '
' failed at 'nope', src/foo.rs:2 +", build.root().join("target/foo").display()))); +}) + +test!(custom_build_env_vars { + let mut p = project("foo"); + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", format!(r#" + use std::os; + fn main() {{ + assert_eq!(os::getenv("OUT_DIR").unwrap(), "{}".to_str()); + assert_eq!(os::getenv("DEPS_DIR").unwrap(), "{}".to_str()); + }} + "#, + p.root().join("target").display(), + p.root().join("target/deps").display())); + assert_that(build.cargo_process("cargo-compile"), execs().with_status(0)); + + + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "{}" + + [[bin]] name = "foo" + "#, build.root().join("target/foo").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("cargo-compile"), execs().with_status(0)); +}) + +test!(custom_build_in_dependency { + let mut p = project("foo"); + let bar = p.root().join("bar"); + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", format!(r#" + use std::os; + fn main() {{ + assert_eq!(os::getenv("OUT_DIR").unwrap(), "{}".to_str()); + assert_eq!(os::getenv("DEPS_DIR").unwrap(), "{}".to_str()); + }} + "#, + p.root().join("target/deps").display(), + p.root().join("target/deps").display())); + assert_that(build.cargo_process("cargo-compile"), execs().with_status(0)); + + + p = p + .file(".cargo/config", format!(r#" + paths = ["{}"] + "#, bar.display()).as_slice()) + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + [dependencies.bar] version = "0.5.0" + "#) + .file("src/foo.rs", r#" + extern crate bar; + fn main() { bar::bar() } + "#) + .file("bar/Cargo.toml", format!(r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "{}" + + [[lib]] name = "bar" + "#, build.root().join("target/foo").display())) + .file("bar/src/bar.rs", r#" + pub fn bar() {} + "#); + assert_that(p.cargo_process("cargo-compile"), + execs().with_status(0)); +})