use std::collections::hash_map::{Entry, HashMap};
use std::env;
use std::hash::{self, Hasher};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use anyhow::{bail, format_err};
use filetime::FileTime;
use log::{debug, info};
use serde::de;
use serde::ser;
use serde::{Deserialize, Serialize};
use crate::core::compiler::unit_graph::UnitDep;
use crate::core::{InternedString, Package};
use crate::util;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::{internal, profile};
use super::custom_build::BuildDeps;
use super::job::{
Freshness::{Dirty, Fresh},
Job, Work,
};
use super::{BuildContext, Context, FileFlavor, Unit};
pub fn prepare_target<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
force: bool,
) -> CargoResult<Job> {
let _p = profile::start(format!(
"fingerprint: {} / {}",
unit.pkg.package_id(),
unit.target.name()
));
let bcx = cx.bcx;
let new = cx.files().fingerprint_dir(unit);
let loc = new.join(&filename(cx, unit));
debug!("fingerprint at: {}", loc.display());
let fingerprint = calculate(cx, unit)?;
let mtime_on_use = cx.bcx.config.cli_unstable().mtime_on_use;
let compare = compare_old_fingerprint(&loc, &*fingerprint, mtime_on_use);
log_compare(unit, &compare);
if compare.is_err() {
let source_id = unit.pkg.package_id().source_id();
let sources = bcx.packages.sources();
let source = sources
.get(source_id)
.ok_or_else(|| internal("missing package source"))?;
source.verify(unit.pkg.package_id())?;
}
if compare.is_ok() && !force {
return Ok(Job::new(Work::noop(), Fresh));
}
let write_fingerprint = if unit.mode.is_run_custom_build() {
let build_script_outputs = Arc::clone(&cx.build_script_outputs);
let pkg_id = unit.pkg.package_id();
let metadata = cx.get_run_build_script_metadata(unit);
let (gen_local, _overridden) = build_script_local_fingerprints(cx, unit);
let output_path = cx.build_explicit_deps[unit].build_script_output.clone();
Work::new(move |_| {
let outputs = build_script_outputs.lock().unwrap();
let output = outputs
.get(pkg_id, metadata)
.expect("output must exist after running");
let deps = BuildDeps::new(&output_path, Some(output));
if let Some(new_local) = (gen_local)(&deps, None)? {
*fingerprint.local.lock().unwrap() = new_local;
}
write_fingerprint(&loc, &fingerprint)
})
} else {
Work::new(move |_| write_fingerprint(&loc, &fingerprint))
};
Ok(Job::new(write_fingerprint, Dirty))
}
#[derive(Clone)]
struct DepFingerprint {
pkg_id: u64,
name: InternedString,
public: bool,
only_requires_rmeta: bool,
fingerprint: Arc<Fingerprint>,
}
#[derive(Serialize, Deserialize)]
pub struct Fingerprint {
rustc: u64,
features: String,
target: u64,
profile: u64,
path: u64,
deps: Vec<DepFingerprint>,
local: Mutex<Vec<LocalFingerprint>>,
#[serde(skip)]
memoized_hash: Mutex<Option<u64>>,
rustflags: Vec<String>,
metadata: u64,
#[serde(skip)]
fs_status: FsStatus,
#[serde(skip)]
outputs: Vec<PathBuf>,
}
enum FsStatus {
Stale,
UpToDate { mtimes: HashMap<PathBuf, FileTime> },
}
impl FsStatus {
fn up_to_date(&self) -> bool {
match self {
FsStatus::UpToDate { .. } => true,
FsStatus::Stale => false,
}
}
}
impl Default for FsStatus {
fn default() -> FsStatus {
FsStatus::Stale
}
}
impl Serialize for DepFingerprint {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
(
&self.pkg_id,
&self.name,
&self.public,
&self.fingerprint.hash(),
)
.serialize(ser)
}
}
impl<'de> Deserialize<'de> for DepFingerprint {
fn deserialize<D>(d: D) -> Result<DepFingerprint, D::Error>
where
D: de::Deserializer<'de>,
{
let (pkg_id, name, public, hash) = <(u64, String, bool, u64)>::deserialize(d)?;
Ok(DepFingerprint {
pkg_id,
name: InternedString::new(&name),
public,
fingerprint: Arc::new(Fingerprint {
memoized_hash: Mutex::new(Some(hash)),
..Fingerprint::new()
}),
only_requires_rmeta: false,
})
}
}
#[derive(Debug, Serialize, Deserialize, Hash)]
enum LocalFingerprint {
Precalculated(String),
CheckDepInfo { dep_info: PathBuf },
RerunIfChanged {
output: PathBuf,
paths: Vec<PathBuf>,
},
RerunIfEnvChanged { var: String, val: Option<String> },
}
enum StaleFile {
Missing(PathBuf),
Changed {
reference: PathBuf,
reference_mtime: FileTime,
stale: PathBuf,
stale_mtime: FileTime,
},
}
impl LocalFingerprint {
fn find_stale_file(
&self,
mtime_cache: &mut HashMap<PathBuf, FileTime>,
pkg_root: &Path,
target_root: &Path,
) -> CargoResult<Option<StaleFile>> {
match self {
LocalFingerprint::CheckDepInfo { dep_info } => {
let dep_info = target_root.join(dep_info);
if let Some(paths) = parse_dep_info(pkg_root, target_root, &dep_info)? {
Ok(find_stale_file(mtime_cache, &dep_info, paths.iter()))
} else {
Ok(Some(StaleFile::Missing(dep_info)))
}
}
LocalFingerprint::RerunIfChanged { output, paths } => Ok(find_stale_file(
mtime_cache,
&target_root.join(output),
paths.iter().map(|p| pkg_root.join(p)),
)),
LocalFingerprint::RerunIfEnvChanged { .. } => Ok(None),
LocalFingerprint::Precalculated(..) => Ok(None),
}
}
fn kind(&self) -> &'static str {
match self {
LocalFingerprint::Precalculated(..) => "precalculated",
LocalFingerprint::CheckDepInfo { .. } => "dep-info",
LocalFingerprint::RerunIfChanged { .. } => "rerun-if-changed",
LocalFingerprint::RerunIfEnvChanged { .. } => "rerun-if-env-changed",
}
}
}
#[derive(Debug)]
struct MtimeSlot(Mutex<Option<FileTime>>);
impl Fingerprint {
fn new() -> Fingerprint {
Fingerprint {
rustc: 0,
target: 0,
profile: 0,
path: 0,
features: String::new(),
deps: Vec::new(),
local: Mutex::new(Vec::new()),
memoized_hash: Mutex::new(None),
rustflags: Vec::new(),
metadata: 0,
fs_status: FsStatus::Stale,
outputs: Vec::new(),
}
}
pub fn clear_memoized(&self) {
*self.memoized_hash.lock().unwrap() = None;
}
fn hash(&self) -> u64 {
if let Some(s) = *self.memoized_hash.lock().unwrap() {
return s;
}
let ret = util::hash_u64(self);
*self.memoized_hash.lock().unwrap() = Some(ret);
ret
}
fn compare(&self, old: &Fingerprint) -> CargoResult<()> {
if self.rustc != old.rustc {
bail!("rust compiler has changed")
}
if self.features != old.features {
bail!(
"features have changed: {} != {}",
self.features,
old.features
)
}
if self.target != old.target {
bail!("target configuration has changed")
}
if self.path != old.path {
bail!("path to the source has changed")
}
if self.profile != old.profile {
bail!("profile configuration has changed")
}
if self.rustflags != old.rustflags {
bail!(
"RUSTFLAGS has changed: {:?} != {:?}",
self.rustflags,
old.rustflags
)
}
if self.metadata != old.metadata {
bail!("metadata changed")
}
let my_local = self.local.lock().unwrap();
let old_local = old.local.lock().unwrap();
if my_local.len() != old_local.len() {
bail!("local lens changed");
}
for (new, old) in my_local.iter().zip(old_local.iter()) {
match (new, old) {
(LocalFingerprint::Precalculated(a), LocalFingerprint::Precalculated(b)) => {
if a != b {
bail!("precalculated components have changed: {} != {}", a, b)
}
}
(
LocalFingerprint::CheckDepInfo { dep_info: adep },
LocalFingerprint::CheckDepInfo { dep_info: bdep },
) => {
if adep != bdep {
bail!("dep info output changed: {:?} != {:?}", adep, bdep)
}
}
(
LocalFingerprint::RerunIfChanged {
output: aout,
paths: apaths,
},
LocalFingerprint::RerunIfChanged {
output: bout,
paths: bpaths,
},
) => {
if aout != bout {
bail!("rerun-if-changed output changed: {:?} != {:?}", aout, bout)
}
if apaths != bpaths {
bail!(
"rerun-if-changed output changed: {:?} != {:?}",
apaths,
bpaths,
)
}
}
(
LocalFingerprint::RerunIfEnvChanged {
var: akey,
val: avalue,
},
LocalFingerprint::RerunIfEnvChanged {
var: bkey,
val: bvalue,
},
) => {
if *akey != *bkey {
bail!("env vars changed: {} != {}", akey, bkey);
}
if *avalue != *bvalue {
bail!(
"env var `{}` changed: previously {:?} now {:?}",
akey,
bvalue,
avalue
)
}
}
(a, b) => bail!(
"local fingerprint type has changed ({} => {})",
b.kind(),
a.kind()
),
}
}
if self.deps.len() != old.deps.len() {
bail!("number of dependencies has changed")
}
for (a, b) in self.deps.iter().zip(old.deps.iter()) {
if a.name != b.name {
let e = format_err!("`{}` != `{}`", a.name, b.name)
.context("unit dependency name changed");
return Err(e.into());
}
if a.fingerprint.hash() != b.fingerprint.hash() {
let e = format_err!(
"new ({}/{:x}) != old ({}/{:x})",
a.name,
a.fingerprint.hash(),
b.name,
b.fingerprint.hash()
)
.context("unit dependency information changed");
return Err(e.into());
}
}
if !self.fs_status.up_to_date() {
bail!("current filesystem status shows we're outdated");
}
bail!("two fingerprint comparison turned up nothing obvious");
}
fn check_filesystem(
&mut self,
mtime_cache: &mut HashMap<PathBuf, FileTime>,
pkg_root: &Path,
target_root: &Path,
) -> CargoResult<()> {
assert!(!self.fs_status.up_to_date());
let mut mtimes = HashMap::new();
for output in self.outputs.iter() {
let mtime = match paths::mtime(output) {
Ok(mtime) => mtime,
Err(e) => {
debug!("failed to get mtime of {:?}: {}", output, e);
return Ok(());
}
};
assert!(mtimes.insert(output.clone(), mtime).is_none());
}
let opt_max = mtimes.iter().max_by_key(|kv| kv.1);
let (max_path, max_mtime) = match opt_max {
Some(mtime) => mtime,
None => {
self.fs_status = FsStatus::UpToDate { mtimes };
return Ok(());
}
};
debug!(
"max output mtime for {:?} is {:?} {}",
pkg_root, max_path, max_mtime
);
for dep in self.deps.iter() {
let dep_mtimes = match &dep.fingerprint.fs_status {
FsStatus::UpToDate { mtimes } => mtimes,
FsStatus::Stale => return Ok(()),
};
let (dep_path, dep_mtime) = if dep.only_requires_rmeta {
dep_mtimes
.iter()
.find(|(path, _mtime)| {
path.extension().and_then(|s| s.to_str()) == Some("rmeta")
})
.expect("failed to find rmeta")
} else {
match dep_mtimes.iter().max_by_key(|kv| kv.1) {
Some(dep_mtime) => dep_mtime,
None => continue,
}
};
debug!(
"max dep mtime for {:?} is {:?} {}",
pkg_root, dep_path, dep_mtime
);
if dep_mtime > max_mtime {
info!(
"dependency on `{}` is newer than we are {} > {} {:?}",
dep.name, dep_mtime, max_mtime, pkg_root
);
return Ok(());
}
}
for local in self.local.get_mut().unwrap().iter() {
if let Some(file) = local.find_stale_file(mtime_cache, pkg_root, target_root)? {
file.log();
return Ok(());
}
}
self.fs_status = FsStatus::UpToDate { mtimes };
debug!("filesystem up-to-date {:?}", pkg_root);
Ok(())
}
}
impl hash::Hash for Fingerprint {
fn hash<H: Hasher>(&self, h: &mut H) {
let Fingerprint {
rustc,
ref features,
target,
path,
profile,
ref deps,
ref local,
metadata,
ref rustflags,
..
} = *self;
let local = local.lock().unwrap();
(
rustc, features, target, path, profile, &*local, metadata, rustflags,
)
.hash(h);
h.write_usize(deps.len());
for DepFingerprint {
pkg_id,
name,
public,
fingerprint,
only_requires_rmeta: _, } in deps
{
pkg_id.hash(h);
name.hash(h);
public.hash(h);
h.write_u64(Fingerprint::hash(fingerprint));
}
}
}
impl hash::Hash for MtimeSlot {
fn hash<H: Hasher>(&self, h: &mut H) {
self.0.lock().unwrap().hash(h)
}
}
impl ser::Serialize for MtimeSlot {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.0
.lock()
.unwrap()
.map(|ft| (ft.unix_seconds(), ft.nanoseconds()))
.serialize(s)
}
}
impl<'de> de::Deserialize<'de> for MtimeSlot {
fn deserialize<D>(d: D) -> Result<MtimeSlot, D::Error>
where
D: de::Deserializer<'de>,
{
let kind: Option<(i64, u32)> = de::Deserialize::deserialize(d)?;
Ok(MtimeSlot(Mutex::new(
kind.map(|(s, n)| FileTime::from_unix_time(s, n)),
)))
}
}
impl DepFingerprint {
fn new<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
parent: &Unit<'a>,
dep: &UnitDep<'a>,
) -> CargoResult<DepFingerprint> {
let fingerprint = calculate(cx, &dep.unit)?;
let pkg_id = if dep.unit.pkg.package_id().source_id().is_path() {
util::hash_u64(dep.unit.pkg.package_id().name())
} else {
util::hash_u64(dep.unit.pkg.package_id())
};
Ok(DepFingerprint {
pkg_id,
name: dep.extern_crate_name,
public: dep.public,
fingerprint,
only_requires_rmeta: cx.only_requires_rmeta(parent, &dep.unit),
})
}
}
impl StaleFile {
fn log(&self) {
match self {
StaleFile::Missing(path) => {
info!("stale: missing {:?}", path);
}
StaleFile::Changed {
reference,
reference_mtime,
stale,
stale_mtime,
} => {
info!("stale: changed {:?}", stale);
info!(" (vs) {:?}", reference);
info!(" {:?} != {:?}", reference_mtime, stale_mtime);
}
}
}
}
fn calculate<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
) -> CargoResult<Arc<Fingerprint>> {
if let Some(s) = cx.fingerprints.get(unit) {
return Ok(Arc::clone(s));
}
let mut fingerprint = if unit.mode.is_run_custom_build() {
calculate_run_custom_build(cx, unit)?
} else if unit.mode.is_doc_test() {
panic!("doc tests do not fingerprint");
} else {
calculate_normal(cx, unit)?
};
let target_root = target_root(cx);
fingerprint.check_filesystem(&mut cx.mtime_cache, unit.pkg.root(), &target_root)?;
let fingerprint = Arc::new(fingerprint);
cx.fingerprints.insert(*unit, Arc::clone(&fingerprint));
Ok(fingerprint)
}
fn calculate_normal<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
) -> CargoResult<Fingerprint> {
let deps = Vec::from(cx.unit_deps(unit));
let mut deps = deps
.into_iter()
.filter(|dep| !dep.unit.target.is_bin())
.map(|dep| DepFingerprint::new(cx, unit, &dep))
.collect::<CargoResult<Vec<_>>>()?;
deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
let target_root = target_root(cx);
let local = if use_dep_info(unit) {
let dep_info = dep_info_loc(cx, unit);
let dep_info = dep_info.strip_prefix(&target_root).unwrap().to_path_buf();
vec![LocalFingerprint::CheckDepInfo { dep_info }]
} else {
let fingerprint = pkg_fingerprint(cx.bcx, unit.pkg)?;
vec![LocalFingerprint::Precalculated(fingerprint)]
};
let outputs = cx
.outputs(unit)?
.iter()
.filter(|output| output.flavor != FileFlavor::DebugInfo)
.map(|output| output.path.clone())
.collect();
let extra_flags = if unit.mode.is_doc() {
cx.bcx.rustdocflags_args(unit)
} else {
cx.bcx.rustflags_args(unit)
}
.to_vec();
let profile_hash = util::hash_u64((&unit.profile, unit.mode, cx.bcx.extra_args_for(unit)));
let m = unit.pkg.manifest().metadata();
let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository));
Ok(Fingerprint {
rustc: util::hash_u64(&cx.bcx.rustc().verbose_version),
target: util::hash_u64(&unit.target),
profile: profile_hash,
path: util::hash_u64(super::path_args(cx.bcx, unit).0),
features: format!("{:?}", unit.features),
deps,
local: Mutex::new(local),
memoized_hash: Mutex::new(None),
metadata,
rustflags: extra_flags,
fs_status: FsStatus::Stale,
outputs,
})
}
fn use_dep_info(unit: &Unit<'_>) -> bool {
!unit.mode.is_doc()
}
fn calculate_run_custom_build<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
) -> CargoResult<Fingerprint> {
assert!(unit.mode.is_run_custom_build());
let (gen_local, overridden) = build_script_local_fingerprints(cx, unit);
let deps = &cx.build_explicit_deps[unit];
let local = (gen_local)(deps, Some(&|| pkg_fingerprint(cx.bcx, unit.pkg)))?.unwrap();
let output = deps.build_script_output.clone();
let deps = if overridden {
vec![]
} else {
let deps = Vec::from(cx.unit_deps(unit));
deps.into_iter()
.map(|dep| DepFingerprint::new(cx, unit, &dep))
.collect::<CargoResult<Vec<_>>>()?
};
Ok(Fingerprint {
local: Mutex::new(local),
rustc: util::hash_u64(&cx.bcx.rustc().verbose_version),
deps,
outputs: if overridden { Vec::new() } else { vec![output] },
..Fingerprint::new()
})
}
fn build_script_local_fingerprints<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
) -> (
Box<
dyn FnOnce(
&BuildDeps,
Option<&dyn Fn() -> CargoResult<String>>,
) -> CargoResult<Option<Vec<LocalFingerprint>>>
+ Send,
>,
bool,
) {
assert!(unit.mode.is_run_custom_build());
if let Some(fingerprint) = build_script_override_fingerprint(cx, unit) {
debug!("override local fingerprints deps {}", unit.pkg);
return (
Box::new(
move |_: &BuildDeps, _: Option<&dyn Fn() -> CargoResult<String>>| {
Ok(Some(vec![fingerprint]))
},
),
true, );
}
let pkg_root = unit.pkg.root().to_path_buf();
let target_dir = target_root(cx);
let calculate =
move |deps: &BuildDeps, pkg_fingerprint: Option<&dyn Fn() -> CargoResult<String>>| {
if deps.rerun_if_changed.is_empty() && deps.rerun_if_env_changed.is_empty() {
match pkg_fingerprint {
Some(f) => {
let s = f()?;
debug!(
"old local fingerprints deps {:?} precalculated={:?}",
pkg_root, s
);
return Ok(Some(vec![LocalFingerprint::Precalculated(s)]));
}
None => return Ok(None),
}
}
Ok(Some(local_fingerprints_deps(deps, &target_dir, &pkg_root)))
};
(Box::new(calculate), false)
}
fn build_script_override_fingerprint<'a, 'cfg>(
cx: &mut Context<'a, 'cfg>,
unit: &Unit<'a>,
) -> Option<LocalFingerprint> {
let build_script_outputs = cx.build_script_outputs.lock().unwrap();
let metadata = cx.get_run_build_script_metadata(unit);
let output = build_script_outputs.get(unit.pkg.package_id(), metadata)?;
let s = format!(
"overridden build state with hash: {}",
util::hash_u64(output)
);
Some(LocalFingerprint::Precalculated(s))
}
fn local_fingerprints_deps(
deps: &BuildDeps,
target_root: &Path,
pkg_root: &Path,
) -> Vec<LocalFingerprint> {
debug!("new local fingerprints deps {:?}", pkg_root);
let mut local = Vec::new();
if !deps.rerun_if_changed.is_empty() {
let output = deps
.build_script_output
.strip_prefix(target_root)
.unwrap()
.to_path_buf();
let paths = deps
.rerun_if_changed
.iter()
.map(|p| p.strip_prefix(pkg_root).unwrap_or(p).to_path_buf())
.collect();
local.push(LocalFingerprint::RerunIfChanged { output, paths });
}
for var in deps.rerun_if_env_changed.iter() {
let val = env::var(var).ok();
local.push(LocalFingerprint::RerunIfEnvChanged {
var: var.clone(),
val,
});
}
local
}
fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> {
debug_assert_ne!(fingerprint.rustc, 0);
let hash = fingerprint.hash();
debug!("write fingerprint ({:x}) : {}", hash, loc.display());
paths::write(loc, util::to_hex(hash).as_bytes())?;
let json = serde_json::to_string(fingerprint).unwrap();
if cfg!(debug_assertions) {
let f: Fingerprint = serde_json::from_str(&json).unwrap();
assert_eq!(f.hash(), hash);
}
paths::write(&loc.with_extension("json"), json.as_bytes())?;
Ok(())
}
pub fn prepare_init<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<()> {
let new1 = cx.files().fingerprint_dir(unit);
if !new1.exists() && !unit.mode.is_doc_test() {
paths::create_dir_all(&new1)?;
}
Ok(())
}
pub fn dep_info_loc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> PathBuf {
cx.files()
.fingerprint_dir(unit)
.join(&format!("dep-{}", filename(cx, unit)))
}
fn target_root(cx: &Context<'_, '_>) -> PathBuf {
cx.bcx.ws.target_dir().into_path_unlocked()
}
fn compare_old_fingerprint(
loc: &Path,
new_fingerprint: &Fingerprint,
mtime_on_use: bool,
) -> CargoResult<()> {
let old_fingerprint_short = paths::read(loc)?;
if mtime_on_use {
let t = FileTime::from_system_time(SystemTime::now());
debug!("mtime-on-use forcing {:?} to {}", loc, t);
filetime::set_file_times(loc, t, t)?;
}
let new_hash = new_fingerprint.hash();
if util::to_hex(new_hash) == old_fingerprint_short && new_fingerprint.fs_status.up_to_date() {
return Ok(());
}
let old_fingerprint_json = paths::read(&loc.with_extension("json"))?;
let old_fingerprint: Fingerprint = serde_json::from_str(&old_fingerprint_json)
.chain_err(|| internal("failed to deserialize json"))?;
debug_assert_eq!(util::to_hex(old_fingerprint.hash()), old_fingerprint_short);
let result = new_fingerprint.compare(&old_fingerprint);
assert!(result.is_err());
result
}
fn log_compare(unit: &Unit<'_>, compare: &CargoResult<()>) {
let ce = match compare {
Ok(..) => return,
Err(e) => e,
};
info!(
"fingerprint error for {}/{:?}/{:?}",
unit.pkg, unit.mode, unit.target,
);
info!(" err: {:?}", ce);
}
pub fn parse_dep_info(
pkg_root: &Path,
target_root: &Path,
dep_info: &Path,
) -> CargoResult<Option<Vec<PathBuf>>> {
let data = match paths::read_bytes(dep_info) {
Ok(data) => data,
Err(_) => return Ok(None),
};
let paths = data
.split(|&x| x == 0)
.filter(|x| !x.is_empty())
.map(|p| {
let ty = match DepInfoPathType::from_byte(p[0]) {
Some(ty) => ty,
None => return Err(internal("dep-info invalid")),
};
let path = util::bytes2path(&p[1..])?;
match ty {
DepInfoPathType::PackageRootRelative => Ok(pkg_root.join(path)),
DepInfoPathType::TargetRootRelative => Ok(target_root.join(path)),
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(paths))
}
fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult<String> {
let source_id = pkg.package_id().source_id();
let sources = bcx.packages.sources();
let source = sources
.get(source_id)
.ok_or_else(|| internal("missing package source"))?;
source.fingerprint(pkg)
}
fn find_stale_file<I>(
mtime_cache: &mut HashMap<PathBuf, FileTime>,
reference: &Path,
paths: I,
) -> Option<StaleFile>
where
I: IntoIterator,
I::Item: AsRef<Path>,
{
let reference_mtime = match paths::mtime(reference) {
Ok(mtime) => mtime,
Err(..) => return Some(StaleFile::Missing(reference.to_path_buf())),
};
for path in paths {
let path = path.as_ref();
let path_mtime = match mtime_cache.entry(path.to_path_buf()) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
let mtime = match paths::mtime(path) {
Ok(mtime) => mtime,
Err(..) => return Some(StaleFile::Missing(path.to_path_buf())),
};
*v.insert(mtime)
}
};
if path_mtime <= reference_mtime {
continue;
}
return Some(StaleFile::Changed {
reference: reference.to_path_buf(),
reference_mtime,
stale: path.to_path_buf(),
stale_mtime: path_mtime,
});
}
debug!(
"all paths up-to-date relative to {:?} mtime={}",
reference, reference_mtime
);
None
}
fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String {
let file_stem = cx.files().file_stem(unit);
let kind = unit.target.kind().description();
let flavor = if unit.mode.is_any_test() {
"test-"
} else if unit.mode.is_doc() {
"doc-"
} else if unit.mode.is_run_custom_build() {
"run-"
} else {
""
};
format!("{}{}-{}", flavor, kind, file_stem)
}
#[repr(u8)]
enum DepInfoPathType {
PackageRootRelative = 1,
TargetRootRelative = 2,
}
impl DepInfoPathType {
fn from_byte(b: u8) -> Option<DepInfoPathType> {
match b {
1 => Some(DepInfoPathType::PackageRootRelative),
2 => Some(DepInfoPathType::TargetRootRelative),
_ => None,
}
}
}
pub fn translate_dep_info(
rustc_dep_info: &Path,
cargo_dep_info: &Path,
rustc_cwd: &Path,
pkg_root: &Path,
target_root: &Path,
allow_package: bool,
) -> CargoResult<()> {
let target = parse_rustc_dep_info(rustc_dep_info)?;
let deps = &target
.get(0)
.ok_or_else(|| internal("malformed dep-info format, no targets".to_string()))?
.1;
let target_root = target_root.canonicalize()?;
let pkg_root = pkg_root.canonicalize()?;
let mut new_contents = Vec::new();
for file in deps {
let abs_file = rustc_cwd.join(file);
let canon_file = abs_file.canonicalize().unwrap_or_else(|_| abs_file.clone());
let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&target_root) {
(DepInfoPathType::TargetRootRelative, stripped)
} else if let Ok(stripped) = canon_file.strip_prefix(&pkg_root) {
if !allow_package {
continue;
}
(DepInfoPathType::PackageRootRelative, stripped)
} else {
(DepInfoPathType::TargetRootRelative, &*abs_file)
};
new_contents.push(ty as u8);
new_contents.extend(util::path2bytes(path)?);
new_contents.push(0);
}
paths::write(cargo_dep_info, &new_contents)?;
Ok(())
}
pub fn parse_rustc_dep_info(rustc_dep_info: &Path) -> CargoResult<Vec<(String, Vec<String>)>> {
let contents = paths::read(rustc_dep_info)?;
contents
.lines()
.filter_map(|l| l.find(": ").map(|i| (l, i)))
.map(|(line, pos)| {
let target = &line[..pos];
let mut deps = line[pos + 2..].split_whitespace();
let mut ret = Vec::new();
while let Some(s) = deps.next() {
let mut file = s.to_string();
while file.ends_with('\\') {
file.pop();
file.push(' ');
file.push_str(deps.next().ok_or_else(|| {
internal("malformed dep-info format, trailing \\".to_string())
})?);
}
ret.push(file);
}
Ok((target.to_string(), ret))
})
.collect()
}