Skip to content

Commit 1dd0215

Browse files
committed
Auto merge of #7137 - ehuss:dep-info-absolute-paths, r=alexcrichton
Fix some issues with absolute paths in dep-info files. There were some issues with how #7030 was handling translating paths in dep-info files. The main consequence is that when coupled with rust-lang/rust#61727 (which has not yet merged), the fingerprint would fail and be considered dirty when it should be fresh. It was joining [`target_root`](https://github.com/rust-lang/cargo/blame/1140c527c4c3b3e2533b9771d67f88509ef7fc16/src/cargo/core/compiler/fingerprint.rs#L1352-L1360) which had 3 different values, but stripping [only one](https://github.com/rust-lang/cargo/blame/1140c527c4c3b3e2533b9771d67f88509ef7fc16/src/cargo/core/compiler/mod.rs#L323). This means for different scenarios (like using `--target`), it was creating incorrect paths in some cases. For example a target having a proc-macro dependency which would be in the host directory. The solution here is to always use CARGO_TARGET_DIR as the base that all relative paths are checked against. This should handle all host/target differences. The tests are marked with `#[ignore]` because 61727 has not yet merged. This includes a second commit (which I can open as a separate PR if needed) which is an alternate solution to #7034. It adds dep-info tracking for registry dependencies. However, it tries to limit which kinds of paths it tracks. It will not track package-relative paths (like source files). It also adds an mtime cache to significantly reduce the number of stat calls (because every dependency was stating every file in sysroot). Finally, I've run some tests using this PR with 61727 in rust-lang/rust. I can confirm that a second build is fresh (it doesn't erroneously rebuild). I can also confirm that the problem in rust-lang/rust#59105 has *finally* been fixed! My confidence in all this is pretty low, but I've been unable to think of any specific ways to make it fail. If anyone has ideas on how to better test this, let me know. Also, feel free to ask questions since I've been thinking about this a lot for the past few weeks, and there is quite a bit of subtle stuff going on.
2 parents 5251d92 + c6e626b commit 1dd0215

File tree

11 files changed

+594
-103
lines changed

11 files changed

+594
-103
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ memchr = "2.1.3"
4747
num_cpus = "1.0"
4848
opener = "0.4"
4949
percent-encoding = "2.0"
50+
remove_dir_all = "0.5.2"
5051
rustfix = "0.4.4"
5152
same-file = "1"
5253
semver = { version = "0.9.0", features = ["serde"] }

crates/resolver-tests/tests/resolve.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::env;
22

33
use cargo::core::dependency::Kind;
44
use cargo::core::{enable_nightly_features, Dependency};
5-
use cargo::util::Config;
5+
use cargo::util::{is_ci, Config};
66

77
use resolver_tests::{
88
assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, dep_req_kind, loc_names, names,
@@ -22,7 +22,7 @@ use proptest::prelude::*;
2222
proptest! {
2323
#![proptest_config(ProptestConfig {
2424
max_shrink_iters:
25-
if env::var("CI").is_ok() || !atty::is(atty::Stream::Stderr) {
25+
if is_ci() || !atty::is(atty::Stream::Stderr) {
2626
// This attempts to make sure that CI will fail fast,
2727
0
2828
} else {

src/cargo/core/compiler/context/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::fmt::Write;
55
use std::path::PathBuf;
66
use std::sync::Arc;
77

8+
use filetime::FileTime;
89
use jobserver::Client;
910

1011
use crate::core::compiler::compilation;
@@ -34,6 +35,7 @@ pub struct Context<'a, 'cfg> {
3435
pub build_script_overridden: HashSet<(PackageId, Kind)>,
3536
pub build_explicit_deps: HashMap<Unit<'a>, BuildDeps>,
3637
pub fingerprints: HashMap<Unit<'a>, Arc<Fingerprint>>,
38+
pub mtime_cache: HashMap<PathBuf, FileTime>,
3739
pub compiled: HashSet<Unit<'a>>,
3840
pub build_scripts: HashMap<Unit<'a>, Arc<BuildScripts>>,
3941
pub links: Links,
@@ -82,6 +84,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
8284
compilation: Compilation::new(bcx)?,
8385
build_state: Arc::new(BuildState::new(&bcx.host_config, &bcx.target_config)),
8486
fingerprints: HashMap::new(),
87+
mtime_cache: HashMap::new(),
8588
compiled: HashSet::new(),
8689
build_scripts: HashMap::new(),
8790
build_explicit_deps: HashMap::new(),

src/cargo/core/compiler/fingerprint.rs

+60-42
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
//! See the `A-rebuild-detection` flag on the issue tracker for more:
188188
//! <https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AA-rebuild-detection>
189189
190-
use std::collections::HashMap;
190+
use std::collections::hash_map::{Entry, HashMap};
191191
use std::env;
192192
use std::fs;
193193
use std::hash::{self, Hasher};
@@ -213,7 +213,7 @@ use super::job::{
213213
Freshness::{Dirty, Fresh},
214214
Job, Work,
215215
};
216-
use super::{BuildContext, Context, FileFlavor, Kind, Unit};
216+
use super::{BuildContext, Context, FileFlavor, Unit};
217217

218218
/// Determines if a `unit` is up-to-date, and if not prepares necessary work to
219219
/// update the persisted fingerprint.
@@ -539,6 +539,7 @@ impl LocalFingerprint {
539539
/// file accesses.
540540
fn find_stale_file(
541541
&self,
542+
mtime_cache: &mut HashMap<PathBuf, FileTime>,
542543
pkg_root: &Path,
543544
target_root: &Path,
544545
) -> CargoResult<Option<StaleFile>> {
@@ -550,7 +551,7 @@ impl LocalFingerprint {
550551
LocalFingerprint::CheckDepInfo { dep_info } => {
551552
let dep_info = target_root.join(dep_info);
552553
if let Some(paths) = parse_dep_info(pkg_root, target_root, &dep_info)? {
553-
Ok(find_stale_file(&dep_info, paths.iter()))
554+
Ok(find_stale_file(mtime_cache, &dep_info, paths.iter()))
554555
} else {
555556
Ok(Some(StaleFile::Missing(dep_info)))
556557
}
@@ -559,6 +560,7 @@ impl LocalFingerprint {
559560
// We need to verify that no paths listed in `paths` are newer than
560561
// the `output` path itself, or the last time the build script ran.
561562
LocalFingerprint::RerunIfChanged { output, paths } => Ok(find_stale_file(
563+
mtime_cache,
562564
&target_root.join(output),
563565
paths.iter().map(|p| pkg_root.join(p)),
564566
)),
@@ -756,7 +758,12 @@ impl Fingerprint {
756758
/// dependencies up to this unit as well. This function assumes that the
757759
/// unit starts out as `FsStatus::Stale` and then it will optionally switch
758760
/// it to `UpToDate` if it can.
759-
fn check_filesystem(&mut self, pkg_root: &Path, target_root: &Path) -> CargoResult<()> {
761+
fn check_filesystem(
762+
&mut self,
763+
mtime_cache: &mut HashMap<PathBuf, FileTime>,
764+
pkg_root: &Path,
765+
target_root: &Path,
766+
) -> CargoResult<()> {
760767
assert!(!self.fs_status.up_to_date());
761768

762769
let mut mtimes = HashMap::new();
@@ -840,7 +847,7 @@ impl Fingerprint {
840847
// files for this package itself. If we do find something log a helpful
841848
// message and bail out so we stay stale.
842849
for local in self.local.get_mut().unwrap().iter() {
843-
if let Some(file) = local.find_stale_file(pkg_root, target_root)? {
850+
if let Some(file) = local.find_stale_file(mtime_cache, pkg_root, target_root)? {
844851
file.log();
845852
return Ok(());
846853
}
@@ -1014,8 +1021,8 @@ fn calculate<'a, 'cfg>(
10141021

10151022
// After we built the initial `Fingerprint` be sure to update the
10161023
// `fs_status` field of it.
1017-
let target_root = target_root(cx, unit);
1018-
fingerprint.check_filesystem(unit.pkg.root(), &target_root)?;
1024+
let target_root = target_root(cx);
1025+
fingerprint.check_filesystem(&mut cx.mtime_cache, unit.pkg.root(), &target_root)?;
10191026

10201027
let fingerprint = Arc::new(fingerprint);
10211028
cx.fingerprints.insert(*unit, Arc::clone(&fingerprint));
@@ -1046,7 +1053,7 @@ fn calculate_normal<'a, 'cfg>(
10461053
// correctly, but otherwise upstream packages like from crates.io or git
10471054
// get bland fingerprints because they don't change without their
10481055
// `PackageId` changing.
1049-
let target_root = target_root(cx, unit);
1056+
let target_root = target_root(cx);
10501057
let local = if use_dep_info(unit) {
10511058
let dep_info = dep_info_loc(cx, unit);
10521059
let dep_info = dep_info.strip_prefix(&target_root).unwrap().to_path_buf();
@@ -1098,13 +1105,10 @@ fn calculate_normal<'a, 'cfg>(
10981105
})
10991106
}
11001107

1101-
// We want to use the mtime for files if we're a path source, but if we're a
1102-
// git/registry source, then the mtime of files may fluctuate, but they won't
1103-
// change so long as the source itself remains constant (which is the
1104-
// responsibility of the source)
1108+
/// Whether or not the fingerprint should track the dependencies from the
1109+
/// dep-info file for this unit.
11051110
fn use_dep_info(unit: &Unit<'_>) -> bool {
1106-
let path = unit.pkg.summary().source_id().is_path();
1107-
!unit.mode.is_doc() && path
1111+
!unit.mode.is_doc()
11081112
}
11091113

11101114
/// Calculate a fingerprint for an "execute a build script" unit. This is an
@@ -1219,8 +1223,8 @@ fn build_script_local_fingerprints<'a, 'cfg>(
12191223
// package. Remember that the fact that this is an `Option` is a bug, but a
12201224
// longstanding bug, in Cargo. Recent refactorings just made it painfully
12211225
// obvious.
1222-
let script_root = cx.files().build_script_run_dir(unit);
12231226
let pkg_root = unit.pkg.root().to_path_buf();
1227+
let target_dir = target_root(cx);
12241228
let calculate =
12251229
move |deps: &BuildDeps, pkg_fingerprint: Option<&dyn Fn() -> CargoResult<String>>| {
12261230
if deps.rerun_if_changed.is_empty() && deps.rerun_if_env_changed.is_empty() {
@@ -1247,7 +1251,7 @@ fn build_script_local_fingerprints<'a, 'cfg>(
12471251
// Ok so now we're in "new mode" where we can have files listed as
12481252
// dependencies as well as env vars listed as dependencies. Process
12491253
// them all here.
1250-
Ok(Some(local_fingerprints_deps(deps, &script_root, &pkg_root)))
1254+
Ok(Some(local_fingerprints_deps(deps, &target_dir, &pkg_root)))
12511255
};
12521256

12531257
// Note that `false` == "not overridden"
@@ -1346,17 +1350,10 @@ pub fn dep_info_loc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> Pa
13461350
.join(&format!("dep-{}", filename(cx, unit)))
13471351
}
13481352

1349-
/// Returns an absolute path that the `unit`'s outputs should always be relative
1350-
/// to. This `target_root` variable is used to store relative path names in
1351-
/// `Fingerprint` instead of absolute pathnames (see module comment).
1352-
fn target_root<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> PathBuf {
1353-
if unit.mode.is_run_custom_build() {
1354-
cx.files().build_script_run_dir(unit)
1355-
} else if unit.kind == Kind::Host {
1356-
cx.files().host_root().to_path_buf()
1357-
} else {
1358-
cx.files().target_root().to_path_buf()
1359-
}
1353+
/// Returns an absolute path that target directory.
1354+
/// All paths are rewritten to be relative to this.
1355+
fn target_root(cx: &Context<'_, '_>) -> PathBuf {
1356+
cx.bcx.ws.target_dir().into_path_unlocked()
13601357
}
13611358

13621359
fn compare_old_fingerprint(
@@ -1429,11 +1426,7 @@ pub fn parse_dep_info(
14291426
}
14301427
})
14311428
.collect::<Result<Vec<_>, _>>()?;
1432-
if paths.is_empty() {
1433-
Ok(None)
1434-
} else {
1435-
Ok(Some(paths))
1436-
}
1429+
Ok(Some(paths))
14371430
}
14381431

14391432
fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult<String> {
@@ -1446,7 +1439,11 @@ fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult<Str
14461439
source.fingerprint(pkg)
14471440
}
14481441

1449-
fn find_stale_file<I>(reference: &Path, paths: I) -> Option<StaleFile>
1442+
fn find_stale_file<I>(
1443+
mtime_cache: &mut HashMap<PathBuf, FileTime>,
1444+
reference: &Path,
1445+
paths: I,
1446+
) -> Option<StaleFile>
14501447
where
14511448
I: IntoIterator,
14521449
I::Item: AsRef<Path>,
@@ -1458,9 +1455,15 @@ where
14581455

14591456
for path in paths {
14601457
let path = path.as_ref();
1461-
let path_mtime = match paths::mtime(path) {
1462-
Ok(mtime) => mtime,
1463-
Err(..) => return Some(StaleFile::Missing(path.to_path_buf())),
1458+
let path_mtime = match mtime_cache.entry(path.to_path_buf()) {
1459+
Entry::Occupied(o) => *o.get(),
1460+
Entry::Vacant(v) => {
1461+
let mtime = match paths::mtime(path) {
1462+
Ok(mtime) => mtime,
1463+
Err(..) => return Some(StaleFile::Missing(path.to_path_buf())),
1464+
};
1465+
*v.insert(mtime)
1466+
}
14641467
};
14651468

14661469
// TODO: fix #5918.
@@ -1547,6 +1550,12 @@ impl DepInfoPathType {
15471550
/// The `rustc_cwd` argument is the absolute path to the cwd of the compiler
15481551
/// when it was invoked.
15491552
///
1553+
/// If the `allow_package` argument is true, then package-relative paths are
1554+
/// included. If it is false, then package-relative paths are skipped and
1555+
/// ignored (typically used for registry or git dependencies where we assume
1556+
/// the source never changes, and we don't want the cost of running `stat` on
1557+
/// all those files).
1558+
///
15501559
/// The serialized Cargo format will contain a list of files, all of which are
15511560
/// relative if they're under `root`. or absolute if they're elsewhere.
15521561
pub fn translate_dep_info(
@@ -1555,26 +1564,35 @@ pub fn translate_dep_info(
15551564
rustc_cwd: &Path,
15561565
pkg_root: &Path,
15571566
target_root: &Path,
1567+
allow_package: bool,
15581568
) -> CargoResult<()> {
15591569
let target = parse_rustc_dep_info(rustc_dep_info)?;
15601570
let deps = &target
15611571
.get(0)
15621572
.ok_or_else(|| internal("malformed dep-info format, no targets".to_string()))?
15631573
.1;
15641574

1575+
let target_root = target_root.canonicalize()?;
1576+
let pkg_root = pkg_root.canonicalize()?;
15651577
let mut new_contents = Vec::new();
15661578
for file in deps {
1567-
let file = rustc_cwd.join(file);
1568-
let (ty, path) = if let Ok(stripped) = file.strip_prefix(pkg_root) {
1569-
(DepInfoPathType::PackageRootRelative, stripped)
1570-
} else if let Ok(stripped) = file.strip_prefix(target_root) {
1579+
// The path may be absolute or relative, canonical or not. Make sure
1580+
// it is canonicalized so we are comparing the same kinds of paths.
1581+
let canon_file = rustc_cwd.join(file).canonicalize()?;
1582+
let abs_file = rustc_cwd.join(file);
1583+
1584+
let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&target_root) {
15711585
(DepInfoPathType::TargetRootRelative, stripped)
1586+
} else if let Ok(stripped) = canon_file.strip_prefix(&pkg_root) {
1587+
if !allow_package {
1588+
continue;
1589+
}
1590+
(DepInfoPathType::PackageRootRelative, stripped)
15721591
} else {
15731592
// It's definitely not target root relative, but this is an absolute path (since it was
15741593
// joined to rustc_cwd) and as such re-joining it later to the target root will have no
15751594
// effect.
1576-
assert!(file.is_absolute(), "{:?} is absolute", file);
1577-
(DepInfoPathType::TargetRootRelative, &*file)
1595+
(DepInfoPathType::TargetRootRelative, &*abs_file)
15781596
};
15791597
new_contents.push(ty as u8);
15801598
new_contents.extend(util::path2bytes(path)?);

src/cargo/core/compiler/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ fn rustc<'a, 'cfg>(
214214
let dep_info_loc = fingerprint::dep_info_loc(cx, unit);
215215

216216
rustc.args(cx.bcx.rustflags_args(unit));
217+
if cx.bcx.config.cli_unstable().binary_dep_depinfo {
218+
rustc.arg("-Zbinary-dep-depinfo");
219+
}
217220
let mut output_options = OutputOptions::new(cx, unit);
218221
let package_id = unit.pkg.package_id();
219222
let target = unit.target.clone();
@@ -223,6 +226,7 @@ fn rustc<'a, 'cfg>(
223226
let exec = exec.clone();
224227

225228
let root_output = cx.files().host_root().to_path_buf();
229+
let target_dir = cx.bcx.ws.target_dir().into_path_unlocked();
226230
let pkg_root = unit.pkg.root().to_path_buf();
227231
let cwd = rustc
228232
.get_cwd()
@@ -317,7 +321,9 @@ fn rustc<'a, 'cfg>(
317321
&dep_info_loc,
318322
&cwd,
319323
&pkg_root,
320-
&root_output,
324+
&target_dir,
325+
// Do not track source files in the fingerprint for registry dependencies.
326+
current_id.source_id().is_path(),
321327
)
322328
.chain_err(|| {
323329
internal(format!(

src/cargo/core/features.rs

+2
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ pub struct CliUnstable {
333333
pub mtime_on_use: bool,
334334
pub install_upgrade: bool,
335335
pub cache_messages: bool,
336+
pub binary_dep_depinfo: bool,
336337
}
337338

338339
impl CliUnstable {
@@ -378,6 +379,7 @@ impl CliUnstable {
378379
"mtime-on-use" => self.mtime_on_use = true,
379380
"install-upgrade" => self.install_upgrade = true,
380381
"cache-messages" => self.cache_messages = true,
382+
"binary-dep-depinfo" => self.binary_dep_depinfo = true,
381383
_ => failure::bail!("unknown `-Z` flag specified: {}", k),
382384
}
383385

src/cargo/util/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,8 @@ pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<
8282
}
8383
Ok(())
8484
}
85+
86+
/// Whether or not this running in a Continuous Integration environment.
87+
pub fn is_ci() -> bool {
88+
std::env::var("CI").is_ok() || std::env::var("TF_BUILD").is_ok()
89+
}

src/cargo/util/progress.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::env;
33
use std::time::{Duration, Instant};
44

55
use crate::core::shell::Verbosity;
6-
use crate::util::{CargoResult, Config};
6+
use crate::util::{is_ci, CargoResult, Config};
77

88
use unicode_width::UnicodeWidthChar;
99

@@ -45,7 +45,7 @@ impl<'cfg> Progress<'cfg> {
4545
Ok(term) => term == "dumb",
4646
Err(_) => false,
4747
};
48-
if cfg.shell().verbosity() == Verbosity::Quiet || dumb || env::var("CI").is_ok() {
48+
if cfg.shell().verbosity() == Verbosity::Quiet || dumb || is_ci() {
4949
return Progress { state: None };
5050
}
5151

0 commit comments

Comments
 (0)