use std::env;
-use std::fs::{self, File, OpenOptions};
+use std::fs::{self, File};
use std::hash::{self, Hasher};
use std::io::prelude::*;
-use std::io::{BufReader, SeekFrom};
+use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
}
let allow_failure = unit.profile.rustc_args.is_some();
+ let target_root = cx.target_root().to_path_buf();
let write_fingerprint = Work::new(move |_| {
- match fingerprint.update_local() {
+ match fingerprint.update_local(&target_root) {
Ok(()) => {}
Err(..) if allow_failure => return Ok(()),
Err(e) => return Err(e)
features: String,
target: u64,
profile: u64,
+ path: u64,
#[serde(serialize_with = "serialize_deps", deserialize_with = "deserialize_deps")]
deps: Vec<(String, Arc<Fingerprint>)>,
local: Vec<LocalFingerprint>,
rustc: 0,
target: 0,
profile: 0,
+ path: 0,
local: vec![LocalFingerprint::Precalculated(String::new())],
features: String::new(),
deps: Vec::new(),
EnvBased(String, Option<String>),
}
+impl LocalFingerprint {
+ fn mtime(root: &Path, mtime: Option<FileTime>, path: &Path)
+ -> LocalFingerprint
+ {
+ let mtime = MtimeSlot(Mutex::new(mtime));
+ assert!(path.is_absolute());
+ let path = path.strip_prefix(root).unwrap_or(path);
+ LocalFingerprint::MtimeBased(mtime, path.to_path_buf())
+ }
+}
+
struct MtimeSlot(Mutex<Option<FileTime>>);
impl Fingerprint {
- fn update_local(&self) -> CargoResult<()> {
+ fn update_local(&self, root: &Path) -> CargoResult<()> {
let mut hash_busted = false;
for local in self.local.iter() {
match *local {
LocalFingerprint::MtimeBased(ref slot, ref path) => {
- let meta = fs::metadata(path)
+ let path = root.join(path);
+ let meta = fs::metadata(&path)
.chain_err(|| {
internal(format!("failed to stat `{}`", path.display()))
})?;
if self.target != old.target {
bail!("target configuration has changed")
}
+ if self.path != old.path {
+ bail!("path to the compiler has changed")
+ }
if self.profile != old.profile {
bail!("profile configuration has changed")
}
if self.rustflags != old.rustflags {
- return Err(internal("RUSTFLAGS has changed"))
+ bail!("RUSTFLAGS has changed")
}
if self.local.len() != old.local.len() {
bail!("local lens changed");
rustc,
ref features,
target,
+ path,
profile,
ref deps,
ref local,
memoized_hash: _,
ref rustflags,
} = *self;
- (rustc, features, target, profile, local, rustflags).hash(h);
+ (rustc, features, target, path, profile, local, rustflags).hash(h);
h.write_usize(deps.len());
for &(ref name, ref fingerprint) in deps {
// And finally, calculate what our own local fingerprint is
let local = if use_dep_info(unit) {
let dep_info = dep_info_loc(cx, unit);
- let mtime = dep_info_mtime_if_fresh(&dep_info)?;
- LocalFingerprint::MtimeBased(MtimeSlot(Mutex::new(mtime)), dep_info)
+ let mtime = dep_info_mtime_if_fresh(unit.pkg, &dep_info)?;
+ LocalFingerprint::mtime(cx.target_root(), mtime, &dep_info)
} else {
let fingerprint = pkg_fingerprint(cx, unit.pkg)?;
LocalFingerprint::Precalculated(fingerprint)
rustc: util::hash_u64(&cx.config.rustc()?.verbose_version),
target: util::hash_u64(&unit.target),
profile: util::hash_u64(&unit.profile),
+ // Note that .0 is hashed here, not .1 which is the cwd. That doesn't
+ // actually affect the output artifact so there's no need to hash it.
+ path: util::hash_u64(&super::path_args(cx, unit).0),
features: format!("{:?}", cx.resolve.features_sorted(unit.pkg.package_id())),
deps: deps,
local: vec![local],
rustc: 0,
target: 0,
profile: 0,
+ path: 0,
features: String::new(),
deps: Vec::new(),
local: local,
// build script.
let state = Arc::clone(&cx.build_state);
let key = (unit.pkg.package_id().clone(), unit.kind);
- let root = unit.pkg.root().to_path_buf();
+ let pkg_root = unit.pkg.root().to_path_buf();
+ let target_root = cx.target_root().to_path_buf();
let write_fingerprint = Work::new(move |_| {
if let Some(output_path) = output_path {
let outputs = state.outputs.lock().unwrap();
if !outputs.rerun_if_changed.is_empty() ||
!outputs.rerun_if_env_changed.is_empty() {
let deps = BuildDeps::new(&output_path, Some(outputs));
- fingerprint.local = local_fingerprints_deps(&deps, &root);
- fingerprint.update_local()?;
+ fingerprint.local = local_fingerprints_deps(&deps, &target_root, &pkg_root);
+ fingerprint.update_local(&target_root)?;
}
}
write_fingerprint(&loc, &fingerprint)
// Ok so now we're in "new mode" where we can have files listed as
// dependencies as well as env vars listed as dependencies. Process them all
// here.
- Ok((local_fingerprints_deps(deps, unit.pkg.root()), Some(output)))
+ Ok((local_fingerprints_deps(deps, cx.target_root(), unit.pkg.root()), Some(output)))
}
-fn local_fingerprints_deps(deps: &BuildDeps, root: &Path) -> Vec<LocalFingerprint> {
+fn local_fingerprints_deps(deps: &BuildDeps, target_root: &Path, pkg_root: &Path)
+ -> Vec<LocalFingerprint>
+{
debug!("new local fingerprints deps");
let mut local = Vec::new();
if !deps.rerun_if_changed.is_empty() {
let output = &deps.build_script_output;
- let deps = deps.rerun_if_changed.iter().map(|p| root.join(p));
+ let deps = deps.rerun_if_changed.iter().map(|p| pkg_root.join(p));
let mtime = mtime_if_fresh(output, deps);
- let mtime = MtimeSlot(Mutex::new(mtime));
- local.push(LocalFingerprint::MtimeBased(mtime, output.clone()));
+ local.push(LocalFingerprint::mtime(target_root, mtime, output));
}
for var in deps.rerun_if_env_changed.iter() {
};
info!("fingerprint error for {}: {}", unit.pkg, ce);
- for cause in ce.iter() {
+ for cause in ce.iter().skip(1) {
info!(" cause: {}", cause);
}
}
// Parse the dep-info into a list of paths
-pub fn parse_dep_info(dep_info: &Path) -> CargoResult<Option<Vec<PathBuf>>> {
+pub fn parse_dep_info(cx: &Context, dep_info: &Path)
+ -> CargoResult<Option<Vec<PathBuf>>>
+{
macro_rules! fs_try {
($e:expr) => (match $e { Ok(e) => e, Err(..) => return Ok(None) })
}
- let mut f = BufReader::new(fs_try!(File::open(dep_info)));
- // see comments in append_current_dir for where this cwd is manifested from.
- let mut cwd = Vec::new();
- if fs_try!(f.read_until(0, &mut cwd)) == 0 {
- return Ok(None)
- }
- let cwd = util::bytes2path(&cwd[..cwd.len()-1])?;
+ let f = BufReader::new(fs_try!(File::open(dep_info)));
let line = match f.lines().next() {
Some(Ok(line)) => line,
_ => return Ok(None),
internal("malformed dep-info format, trailing \\".to_string())
})?);
}
- paths.push(cwd.join(&file));
+
+ // Note that paths emitted in dep info files may be relative, but due to
+ // `path_args` in the module above this the relative paths are always
+ // relative to the root of a workspace.
+ paths.push(cx.ws.root().join(&file));
}
Ok(Some(paths))
}
-fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult<Option<FileTime>> {
- if let Some(paths) = parse_dep_info(dep_info)? {
+fn dep_info_mtime_if_fresh(cx: &Context, dep_info: &Path)
+ -> CargoResult<Option<FileTime>>
+{
+ if let Some(paths) = parse_dep_info(cx, dep_info)? {
Ok(mtime_if_fresh(dep_info, paths.iter()))
} else {
Ok(None)
};
format!("{}{}-{}", flavor, kind, file_stem)
}
-
-// The dep-info files emitted by the compiler all have their listed paths
-// relative to whatever the current directory was at the time that the compiler
-// was invoked. As the current directory may change over time, we need to record
-// what that directory was at the beginning of the file so we can know about it
-// next time.
-pub fn append_current_dir(path: &Path, cwd: &Path) -> CargoResult<()> {
- debug!("appending {} <- {}", path.display(), cwd.display());
- let mut f = OpenOptions::new().read(true).write(true).open(path)?;
- let mut contents = Vec::new();
- f.read_to_end(&mut contents)?;
- f.seek(SeekFrom::Start(0))?;
- f.write_all(util::path2bytes(cwd)?)?;
- f.write_all(&[0])?;
- f.write_all(&contents)?;
- Ok(())
-}
root.join(&cx.file_stem(unit))
}.with_extension("d");
let dep_info_loc = fingerprint::dep_info_loc(cx, unit);
- let cwd = cx.config.cwd().to_path_buf();
rustc.args(&cx.incremental_args(unit)?);
rustc.args(&cx.rustflags_args(unit)?);
internal(format!("could not rename dep info: {:?}",
rustc_dep_info_loc))
})?;
- fingerprint::append_current_dir(&dep_info_loc, &cwd)?;
}
Ok(())
unit: &Unit<'a>) -> CargoResult<Work> {
let mut rustdoc = cx.compilation.rustdoc_process(unit.pkg)?;
rustdoc.inherit_jobserver(&cx.jobserver);
- rustdoc.arg("--crate-name").arg(&unit.target.crate_name())
- .cwd(cx.config.cwd())
- .arg(&root_path(cx, unit));
+ rustdoc.arg("--crate-name").arg(&unit.target.crate_name());
+ add_path_args(cx, unit, &mut rustdoc);
if unit.kind != Kind::Host {
if let Some(target) = cx.requested_target() {
}
// The path that we pass to rustc is actually fairly important because it will
-// show up in error messages and the like. For this reason we take a few moments
-// to ensure that something shows up pretty reasonably.
+// show up in error messages (important for readability), debug information
+// (important for caching), etc. As a result we need to be pretty careful how we
+// actually invoke rustc.
//
-// The heuristic here is fairly simple, but the key idea is that the path is
-// always "relative" to the current directory in order to be found easily. The
-// path is only actually relative if the current directory is an ancestor if it.
-// This means that non-path dependencies (git/registry) will likely be shown as
-// absolute paths instead of relative paths.
-fn root_path(cx: &Context, unit: &Unit) -> PathBuf {
- let absolute = unit.pkg.root().join(unit.target.src_path());
- let cwd = cx.config.cwd();
- if absolute.starts_with(cwd) {
- util::without_prefix(&absolute, cwd).map(|s| {
- s.to_path_buf()
- }).unwrap_or(absolute)
- } else {
- absolute
+// In general users don't expect `cargo build` to cause rebuilds if you change
+// directories. That could be if you just change directories in the project or
+// if you literally move the whole project wholesale to a new directory. As a
+// result we mostly don't factor in `cwd` to this calculation. Instead we try to
+// track the workspace as much as possible and we update the current directory
+// of rustc/rustdoc where approrpriate.
+//
+// The first returned value here is the argument to pass to rustc, and the
+// second is the cwd that rustc should operate in.
+fn path_args(cx: &Context, unit: &Unit) -> (PathBuf, Option<PathBuf>) {
+ let ws_root = cx.ws.root();
+ let src = unit.target.src_path();
+ assert!(src.is_absolute());
+ match src.strip_prefix(ws_root) {
+ Ok(path) => (path.to_path_buf(), Some(ws_root.to_path_buf())),
+ Err(_) => (src.to_path_buf(), None),
+ }
+}
+
+fn add_path_args(cx: &Context, unit: &Unit, cmd: &mut ProcessBuilder) {
+ let (arg, cwd) = path_args(cx, unit);
+ cmd.arg(arg);
+ if let Some(cwd) = cwd {
+ cmd.cwd(cwd);
}
}
} = *unit.profile;
assert!(!run_custom_build);
- // Move to cwd so the root_path() passed below is actually correct
- cmd.cwd(cx.config.cwd());
-
cmd.arg("--crate-name").arg(&unit.target.crate_name());
- cmd.arg(&root_path(cx, unit));
+ add_path_args(cx, unit, cmd);
match cx.config.shell().color_choice() {
ColorChoice::Always => { cmd.arg("--color").arg("always"); }