Don't recompile git deps so frequently

The previous logic for recompiling any dependency was almost entirely based on
the mtimes of the relevant input files. This isn't quite what's desired for git
and registry dependencies because the mtime could be fluctuating while the files
aren't changing. For example:

1. Project A checks out git repo C at revision C1
2. Project A builds, records mtimes of C
3. Project B checks out git repo C at revision C2
4. Project B builds, records new mtimes of C
5. Project A is rebuilt, rebuilding C b/c mtimes are different

In step 5 here C should not be rebuilt because the revision didn't actually
change.

This commit alters git/registry dependencies to completely bypass the --dep-info
emitted and only rely on the git/registry source to inform what the fingerprint
is. This is the revision/version, respectively, and should be all that's
necessary to track changes to the repo and trigger a rebuild.
This commit is contained in:
Alex Crichton 2014-09-10 07:26:42 -07:00
parent ef959038b7
commit c0127391bf
2 changed files with 81 additions and 4 deletions

View file

@ -2,7 +2,7 @@ use std::hash::{Hash, Hasher};
use std::hash::sip::SipHasher;
use std::io::{fs, File, UserRWX, BufferedReader};
use core::{Package, Target};
use core::{Package, Target, PathKind};
use util;
use util::hex::short_hash;
use util::{CargoResult, Fresh, Dirty, Freshness, internal, Require, profile};
@ -47,18 +47,31 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target,
let filename = filename(target);
let old_loc = old.join(filename.as_slice());
let new_loc = new.join(filename.as_slice());
let doc = target.get_profile().is_doc();
// We want to use the package fingerprint if we're either a doc target or a
// path source. If we're a git/registry source, then the mtime of files may
// fluctuate, but they won't change so long as the source itself remains
// constant (which is the responsibility of the source)
let use_pkg = {
let doc = target.get_profile().is_doc();
let path = match pkg.get_summary().get_source_id().kind {
PathKind => true,
_ => false,
};
doc || !path
};
debug!("fingerprint at: {}", new_loc.display());
// First bit of the freshness calculation, whether the dep-info file
// indicates that the target is fresh.
let (old_dep_info, new_dep_info) = dep_info_loc(cx, pkg, target, kind);
let are_files_fresh = doc || try!(calculate_target_fresh(pkg, &old_dep_info));
let are_files_fresh = use_pkg ||
try!(calculate_target_fresh(pkg, &old_dep_info));
// Second bit of the freshness calculation, whether rustc itself and the
// target are fresh.
let rustc_fingerprint = if doc {
let rustc_fingerprint = if use_pkg {
mk_fingerprint(cx, &(target, try!(calculate_pkg_fingerprint(cx, pkg))))
} else {
mk_fingerprint(cx, target)

View file

@ -1080,3 +1080,67 @@ test!(git_name_not_always_needed {
{compiling} foo v0.5.0 ({url})
", updating = UPDATING, compiling = COMPILING, url = p.url(), bar = p2.url())));
})
test!(git_repo_changing_no_rebuild {
let bar = git_repo("bar", |project| {
project.file("Cargo.toml", r#"
[package]
name = "bar"
version = "0.5.0"
authors = ["wycats@example.com"]
"#)
.file("src/lib.rs", "pub fn bar() -> int { 1 }")
}).assert();
// Lock p1 to the first rev in the git repo
let p1 = project("p1")
.file("Cargo.toml", format!(r#"
[project]
name = "p1"
version = "0.5.0"
authors = []
build = 'true'
[dependencies.bar]
git = '{}'
"#, bar.url()).as_slice())
.file("src/main.rs", "fn main() {}");
p1.build();
p1.root().move_into_the_past().assert();
assert_that(p1.process(cargo_dir().join("cargo")).arg("build"),
execs().with_stdout(format!("\
{updating} git repository `{bar}`
{compiling} bar v0.5.0 ({bar}#[..])
{compiling} p1 v0.5.0 ({url})
", updating = UPDATING, compiling = COMPILING, url = p1.url(), bar = bar.url())));
// Make a commit to lock p2 to a different rev
File::create(&bar.root().join("src/lib.rs")).write_str(r#"
pub fn bar() -> int { 2 }
"#).assert();
bar.process("git").args(["add", "."]).exec_with_output().assert();
bar.process("git").args(["commit", "-m", "test"]).exec_with_output()
.assert();
// Lock p2 to the second rev
let p2 = project("p2")
.file("Cargo.toml", format!(r#"
[project]
name = "p2"
version = "0.5.0"
authors = []
[dependencies.bar]
git = '{}'
"#, bar.url()).as_slice())
.file("src/main.rs", "fn main() {}");
assert_that(p2.cargo_process("build"),
execs().with_stdout(format!("\
{updating} git repository `{bar}`
{compiling} bar v0.5.0 ({bar}#[..])
{compiling} p2 v0.5.0 ({url})
", updating = UPDATING, compiling = COMPILING, url = p2.url(), bar = bar.url())));
// And now for the real test! Make sure that p1 doesn't get rebuilt
// even though the git repo has changed.
assert_that(p1.process(cargo_dir().join("cargo")).arg("build"),
execs().with_stdout(""));
})