diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 02ea5a47f62..d892a651721 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -563,7 +563,8 @@ fn log_compare(unit: &Unit, compare: &CargoResult<()>) { } } -fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { +// Parse the dep-info into a list of paths +pub fn parse_dep_info(dep_info: &Path) -> CargoResult>> { macro_rules! fs_try { ($e:expr) => (match $e { Ok(e) => e, Err(..) => return Ok(None) }) } @@ -600,8 +601,15 @@ fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { } paths.push(cwd.join(&file)); } + Ok(Some(paths)) +} - Ok(mtime_if_fresh(&dep_info, paths.iter())) +fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { + if let Some(paths) = parse_dep_info(dep_info)? { + Ok(mtime_if_fresh(&dep_info, paths.iter())) + } else { + Ok(None) + } } fn pkg_fingerprint(cx: &Context, pkg: &Package) -> CargoResult { diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 370b7e1dca4..3fb4033ed21 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -18,6 +18,8 @@ use util::Freshness; use self::job::{Job, Work}; use self::job_queue::JobQueue; +use self::output_depinfo::output_depinfo; + pub use self::compilation::Compilation; pub use self::context::{Context, Unit}; pub use self::custom_build::{BuildOutput, BuildMap, BuildScripts}; @@ -30,6 +32,7 @@ mod job; mod job_queue; mod layout; mod links; +mod output_depinfo; #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)] pub enum Kind { Host, Target } @@ -184,6 +187,8 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, .or_insert(HashSet::new()) .extend(feats.iter().map(|feat| format!("feature=\"{}\"", feat))); } + + output_depinfo(&mut cx, unit)?; } for (&(ref pkg, _), output) in cx.build_state.outputs.lock().unwrap().iter() { diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs new file mode 100644 index 00000000000..c559e4741ee --- /dev/null +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -0,0 +1,91 @@ +use std::collections::HashSet; +use std::io::{Write, BufWriter, ErrorKind}; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; + +use ops::{Context, Unit}; +use util::{CargoResult, internal}; +use ops::cargo_rustc::fingerprint; + +fn render_filename>(path: P, basedir: Option<&str>) -> CargoResult { + let path = path.as_ref(); + let relpath = match basedir { + None => path, + Some(base) => match path.strip_prefix(base) { + Ok(relpath) => relpath, + _ => path, + } + }; + relpath.to_str().ok_or(internal("path not utf-8")).map(|f| f.replace(" ", "\\ ")) +} + +fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context<'a, 'b>, + unit: &Unit<'a>, visited: &mut HashSet>) -> CargoResult<()> +{ + if !visited.insert(unit.clone()) { + return Ok(()); + } + + // Add dependencies from rustc dep-info output (stored in fingerprint directory) + let dep_info_loc = fingerprint::dep_info_loc(context, unit); + if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? { + for path in paths { + deps.insert(path); + } + } else { + debug!("can't find dep_info for {:?} {:?}", + unit.pkg.package_id(), unit.profile); + return Err(internal("dep_info missing")); + } + + // Add rerun-if-changed dependencies + let key = (unit.pkg.package_id().clone(), unit.kind); + if let Some(output) = context.build_state.outputs.lock().unwrap().get(&key) { + for path in &output.rerun_if_changed { + deps.insert(path.into()); + } + } + + // Recursively traverse all transitive dependencies + for dep_unit in &context.dep_targets(unit)? { + let source_id = dep_unit.pkg.package_id().source_id(); + if source_id.is_path() { + add_deps_for_unit(deps, context, dep_unit, visited)?; + } + } + Ok(()) +} + +pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) -> CargoResult<()> { + let mut deps = HashSet::new(); + let mut visited = HashSet::new(); + let success = add_deps_for_unit(&mut deps, context, unit, &mut visited).is_ok(); + let basedir = None; // TODO + for (_filename, link_dst, _linkable) in context.target_filenames(unit)? { + if let Some(link_dst) = link_dst { + let output_path = link_dst.with_extension("d"); + if success { + let mut outfile = BufWriter::new(File::create(output_path)?); + let target_fn = render_filename(link_dst, basedir)?; + write!(outfile, "{}:", target_fn)?; + for dep in &deps { + write!(outfile, " {}", render_filename(dep, basedir)?)?; + } + writeln!(outfile, "")?; + } else { + // dep-info generation failed, so delete output file. This will usually + // cause the build system to always rerun the build rule, which is correct + // if inefficient. + match fs::remove_file(output_path) { + Err(err) => { + if err.kind() != ErrorKind::NotFound { + return Err(err.into()); + } + } + _ => () + } + } + } + } + Ok(()) +} diff --git a/tests/dep-info.rs b/tests/dep-info.rs new file mode 100644 index 00000000000..2f0513e7b7e --- /dev/null +++ b/tests/dep-info.rs @@ -0,0 +1,79 @@ +extern crate cargotest; +extern crate hamcrest; + +use cargotest::support::{basic_bin_manifest, main_file, execs, project}; +use hamcrest::{assert_that, existing_file}; + +#[test] +fn build_dep_info() { + let p = project("foo") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/foo.rs", &main_file(r#""i am foo""#, &[])); + + assert_that(p.cargo_process("build"), execs().with_status(0)); + + let depinfo_bin_path = &p.bin("foo").with_extension("d"); + + assert_that(depinfo_bin_path, existing_file()); +} + +#[test] +fn build_dep_info_lib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["lib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "lib").with_extension("d"), existing_file()); +} + + +#[test] +fn build_dep_info_rlib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["rlib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "rlib").with_extension("d"), existing_file()); +} + +#[test] +fn build_dep_info_dylib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["dylib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "dylib").with_extension("d"), existing_file()); +}