Skip to content

Commit 609371f

Browse files
committed
Auto merge of #3557 - raphlinus:master, r=alexcrichton
Add dep-info generation Work in progress: add a --dep-info flag to cargo build (and also rustc) that outputs dependency information in a form compatible with make and ninja, to a specified file. This will help in integrating into other build systems.
2 parents 385e243 + 5cb6995 commit 609371f

File tree

4 files changed

+185
-2
lines changed

4 files changed

+185
-2
lines changed

src/cargo/ops/cargo_rustc/fingerprint.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,8 @@ fn log_compare(unit: &Unit, compare: &CargoResult<()>) {
563563
}
564564
}
565565

566-
fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult<Option<FileTime>> {
566+
// Parse the dep-info into a list of paths
567+
pub fn parse_dep_info(dep_info: &Path) -> CargoResult<Option<Vec<PathBuf>>> {
567568
macro_rules! fs_try {
568569
($e:expr) => (match $e { Ok(e) => e, Err(..) => return Ok(None) })
569570
}
@@ -600,8 +601,15 @@ fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult<Option<FileTime>> {
600601
}
601602
paths.push(cwd.join(&file));
602603
}
604+
Ok(Some(paths))
605+
}
603606

604-
Ok(mtime_if_fresh(&dep_info, paths.iter()))
607+
fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult<Option<FileTime>> {
608+
if let Some(paths) = parse_dep_info(dep_info)? {
609+
Ok(mtime_if_fresh(&dep_info, paths.iter()))
610+
} else {
611+
Ok(None)
612+
}
605613
}
606614

607615
fn pkg_fingerprint(cx: &Context, pkg: &Package) -> CargoResult<String> {

src/cargo/ops/cargo_rustc/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use util::Freshness;
1818
use self::job::{Job, Work};
1919
use self::job_queue::JobQueue;
2020

21+
use self::output_depinfo::output_depinfo;
22+
2123
pub use self::compilation::Compilation;
2224
pub use self::context::{Context, Unit};
2325
pub use self::custom_build::{BuildOutput, BuildMap, BuildScripts};
@@ -30,6 +32,7 @@ mod job;
3032
mod job_queue;
3133
mod layout;
3234
mod links;
35+
mod output_depinfo;
3336

3437
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
3538
pub enum Kind { Host, Target }
@@ -184,6 +187,8 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>,
184187
.or_insert(HashSet::new())
185188
.extend(feats.iter().map(|feat| format!("feature=\"{}\"", feat)));
186189
}
190+
191+
output_depinfo(&mut cx, unit)?;
187192
}
188193

189194
for (&(ref pkg, _), output) in cx.build_state.outputs.lock().unwrap().iter() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use std::collections::HashSet;
2+
use std::io::{Write, BufWriter, ErrorKind};
3+
use std::fs::{self, File};
4+
use std::path::{Path, PathBuf};
5+
6+
use ops::{Context, Unit};
7+
use util::{CargoResult, internal};
8+
use ops::cargo_rustc::fingerprint;
9+
10+
fn render_filename<P: AsRef<Path>>(path: P, basedir: Option<&str>) -> CargoResult<String> {
11+
let path = path.as_ref();
12+
let relpath = match basedir {
13+
None => path,
14+
Some(base) => match path.strip_prefix(base) {
15+
Ok(relpath) => relpath,
16+
_ => path,
17+
}
18+
};
19+
relpath.to_str().ok_or(internal("path not utf-8")).map(|f| f.replace(" ", "\\ "))
20+
}
21+
22+
fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet<PathBuf>, context: &mut Context<'a, 'b>,
23+
unit: &Unit<'a>, visited: &mut HashSet<Unit<'a>>) -> CargoResult<()>
24+
{
25+
if !visited.insert(unit.clone()) {
26+
return Ok(());
27+
}
28+
29+
// Add dependencies from rustc dep-info output (stored in fingerprint directory)
30+
let dep_info_loc = fingerprint::dep_info_loc(context, unit);
31+
if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? {
32+
for path in paths {
33+
deps.insert(path);
34+
}
35+
} else {
36+
debug!("can't find dep_info for {:?} {:?}",
37+
unit.pkg.package_id(), unit.profile);
38+
return Err(internal("dep_info missing"));
39+
}
40+
41+
// Add rerun-if-changed dependencies
42+
let key = (unit.pkg.package_id().clone(), unit.kind);
43+
if let Some(output) = context.build_state.outputs.lock().unwrap().get(&key) {
44+
for path in &output.rerun_if_changed {
45+
deps.insert(path.into());
46+
}
47+
}
48+
49+
// Recursively traverse all transitive dependencies
50+
for dep_unit in &context.dep_targets(unit)? {
51+
let source_id = dep_unit.pkg.package_id().source_id();
52+
if source_id.is_path() {
53+
add_deps_for_unit(deps, context, dep_unit, visited)?;
54+
}
55+
}
56+
Ok(())
57+
}
58+
59+
pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) -> CargoResult<()> {
60+
let mut deps = HashSet::new();
61+
let mut visited = HashSet::new();
62+
let success = add_deps_for_unit(&mut deps, context, unit, &mut visited).is_ok();
63+
let basedir = None; // TODO
64+
for (_filename, link_dst, _linkable) in context.target_filenames(unit)? {
65+
if let Some(link_dst) = link_dst {
66+
let output_path = link_dst.with_extension("d");
67+
if success {
68+
let mut outfile = BufWriter::new(File::create(output_path)?);
69+
let target_fn = render_filename(link_dst, basedir)?;
70+
write!(outfile, "{}:", target_fn)?;
71+
for dep in &deps {
72+
write!(outfile, " {}", render_filename(dep, basedir)?)?;
73+
}
74+
writeln!(outfile, "")?;
75+
} else {
76+
// dep-info generation failed, so delete output file. This will usually
77+
// cause the build system to always rerun the build rule, which is correct
78+
// if inefficient.
79+
match fs::remove_file(output_path) {
80+
Err(err) => {
81+
if err.kind() != ErrorKind::NotFound {
82+
return Err(err.into());
83+
}
84+
}
85+
_ => ()
86+
}
87+
}
88+
}
89+
}
90+
Ok(())
91+
}

tests/dep-info.rs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
extern crate cargotest;
2+
extern crate hamcrest;
3+
4+
use cargotest::support::{basic_bin_manifest, main_file, execs, project};
5+
use hamcrest::{assert_that, existing_file};
6+
7+
#[test]
8+
fn build_dep_info() {
9+
let p = project("foo")
10+
.file("Cargo.toml", &basic_bin_manifest("foo"))
11+
.file("src/foo.rs", &main_file(r#""i am foo""#, &[]));
12+
13+
assert_that(p.cargo_process("build"), execs().with_status(0));
14+
15+
let depinfo_bin_path = &p.bin("foo").with_extension("d");
16+
17+
assert_that(depinfo_bin_path, existing_file());
18+
}
19+
20+
#[test]
21+
fn build_dep_info_lib() {
22+
let p = project("foo")
23+
.file("Cargo.toml", r#"
24+
[package]
25+
name = "foo"
26+
version = "0.0.1"
27+
authors = []
28+
29+
[[example]]
30+
name = "ex"
31+
crate-type = ["lib"]
32+
"#)
33+
.file("src/lib.rs", "")
34+
.file("examples/ex.rs", "");
35+
36+
assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0));
37+
assert_that(&p.example_lib("ex", "lib").with_extension("d"), existing_file());
38+
}
39+
40+
41+
#[test]
42+
fn build_dep_info_rlib() {
43+
let p = project("foo")
44+
.file("Cargo.toml", r#"
45+
[package]
46+
name = "foo"
47+
version = "0.0.1"
48+
authors = []
49+
50+
[[example]]
51+
name = "ex"
52+
crate-type = ["rlib"]
53+
"#)
54+
.file("src/lib.rs", "")
55+
.file("examples/ex.rs", "");
56+
57+
assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0));
58+
assert_that(&p.example_lib("ex", "rlib").with_extension("d"), existing_file());
59+
}
60+
61+
#[test]
62+
fn build_dep_info_dylib() {
63+
let p = project("foo")
64+
.file("Cargo.toml", r#"
65+
[package]
66+
name = "foo"
67+
version = "0.0.1"
68+
authors = []
69+
70+
[[example]]
71+
name = "ex"
72+
crate-type = ["dylib"]
73+
"#)
74+
.file("src/lib.rs", "")
75+
.file("examples/ex.rs", "");
76+
77+
assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0));
78+
assert_that(&p.example_lib("ex", "dylib").with_extension("d"), existing_file());
79+
}

0 commit comments

Comments
 (0)