Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/compiler/fingerprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
//! `__CARGO_DEFAULT_LIB_METADATA`[^4] | | ✓ | ✓
//! `__CARGO_RUSTC_BOOTSTRAP_WS_REMAP`[^2] | ✓ | |
//! `package_id` | | ✓ | ✓
//! Target src path relative to ws | ✓ | |
//! Target src path relative to ws[^9] | ✓ | |
//! Target flags (test/bench/for_host/edition) | ✓ | |
//! -C incremental=… flag | ✓ | |
//! mtime of sources | ✓[^3] | |
Expand Down Expand Up @@ -111,6 +111,9 @@
//!
//! [^8]: including `-Cextra-filename`
//!
//! [^9]: Absolute source paths, for example registry deps,
//! are hashed after remapping when `trim-paths` is enabled.
//!
//! When deciding what should go in the Metadata vs the Fingerprint, consider
//! that some files (like dylibs) do not have a hash in their filename. Thus,
//! if a value changes, only the fingerprint will detect the change (consider,
Expand Down Expand Up @@ -396,6 +399,7 @@ use std::time::SystemTime;
use anyhow::Context as _;
use anyhow::format_err;
use cargo_util::paths;
use cargo_util_schemas::manifest::TomlTrimPaths;
use filetime::FileTime;
use serde::de;
use serde::ser;
Expand Down Expand Up @@ -1710,13 +1714,35 @@ fn calculate_normal(
let mut declared_features = unit.pkg.summary().features().keys().collect::<Vec<_>>();
declared_features.sort(); // to avoid useless rebuild if the user orders it's features
// differently

let src_path = {
// 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.
let src_path = path_args(build_runner.bcx.ws, unit).0;

// Some CI has random CI path for different runs.
// By tracking remapped paths, we avoid rebuilds when `CARGO_HOME` moves.
// See rust-lang/cargo#10915
let should_remap = match unit.profile.trim_paths {
TomlTrimPaths::None => false,
TomlTrimPaths::Object | TomlTrimPaths::All => true,
};

if should_remap
&& src_path.is_absolute()
&& let Some(remapped) = super::trim_paths::remap_path(build_runner, unit, &src_path)
{
remapped
} else {
src_path
}
};

Ok(Fingerprint {
rustc: util::hash_u64(&build_runner.bcx.rustc().verbose_version),
target: util::hash_u64(&unit.target),
profile: profile_hash,
// 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(path_args(build_runner.bcx.ws, unit).0),
path: util::hash_u64(src_path),
features: format!("{:?}", unit.features),
declared_features: format!("{declared_features:?}"),
deps,
Expand Down
35 changes: 26 additions & 9 deletions src/compiler/trim_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,32 @@ pub(crate) fn trim_paths_args(
///
/// [RFC 3127]: https://rust-lang.github.io/rfcs/3127-trim-paths.html
pub(crate) fn trim_paths_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
let mut remaps = Vec::with_capacity(4);
remaps.extend(
package_remap(build_runner, unit)
.into_iter()
.map(join_remap),
);
remaps.push(join_remap(build_dir_remap(build_runner)));
remaps.push(join_remap(sysroot_remap(build_runner, unit)));
remaps
remap_pairs(build_runner, unit).map(join_remap).collect()
}

/// Remaps `path` the same way rustc does with [`trim_paths_remap`] rules,
/// like the last takes precedence.
///
/// `None` if no rule matches.
pub(crate) fn remap_path(
build_runner: &BuildRunner<'_, '_>,
unit: &Unit,
path: &Path,
) -> Option<PathBuf> {
remap_pairs(build_runner, unit)
.filter_map(|(from, to)| {
let rest = path.strip_prefix(&from).ok()?;
Some(Path::new(&to).join(rest))
})
.last()
}

/// Like [`trim_paths_remap`] but yields `(from, to)` pairs.
fn remap_pairs(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> impl Iterator<Item = RemapPair> {
package_remap(build_runner, unit).into_iter().chain([
build_dir_remap(build_runner),
sysroot_remap(build_runner, unit),
])
}

fn join_remap((from, to): RemapPair) -> OsString {
Expand Down
177 changes: 177 additions & 0 deletions tests/testsuite/profile_trim_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2203,3 +2203,180 @@ fn path_dependency_outside_workspace_moved_trim_paths_object() {
fn path_dependency_outside_workspace_moved_trim_paths_all() {
path_dep_outside_ws_and_move("all", true);
}

/// Builds a package and then moves `CARGO_HOME` to a new location.
fn cargo_home_moved_trim_paths(
trim_paths: &str,
) -> (cargo_test_support::Project, std::path::PathBuf) {
Package::new("bar", "0.0.1")
.file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
.file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#)
.publish();
let git_project = git::new("baz", |project| {
project
.file("Cargo.toml", &basic_manifest("baz", "0.0.1"))
.file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#)
});
let url = git_project.url();

let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"

[dependencies]
bar = "0.0.1"
baz = {{ git = "{url}" }}

[profile.dev]
trim-paths = "{trim_paths}"
"#
),
)
.file("src/main.rs", "fn main() { bar::f(); baz::f(); }")
.build();

p.cargo("build").run();

let new_cargo_home = paths::home().join(".cargo-moved");
std::fs::rename(paths::cargo_home(), &new_cargo_home).unwrap();

(p, new_cargo_home)
}

#[cargo_test]
fn cargo_home_moved_trim_paths_none() {
let (p, new_cargo_home) = cargo_home_moved_trim_paths("none");

p.cargo("build --verbose")
.env("CARGO_HOME", &new_cargo_home)
.with_stderr_data(
str![[r#"
[DIRTY] bar v0.0.1: the path to the source changed
[COMPILING] bar v0.0.1
[RUNNING] `rustc --crate-name bar [..]`
[DIRTY] baz v0.0.1 ([ROOTURL]/baz#[..]): the path to the source changed
[COMPILING] baz v0.0.1 ([ROOTURL]/baz#[..])
[RUNNING] `rustc --crate-name baz [..]`
[COMPILING] foo v0.0.1 ([ROOT]/foo)
[RUNNING] `rustc --crate-name foo [..]`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[DIRTY] foo v0.0.1 ([ROOT]/foo): info of dependency `[..]` changed

"#]]
.unordered(),
)
.run();

p.process(&p.bin("foo"))
.with_stdout_data(str![[r#"
[ROOT]/home/.cargo-moved/registry/src/-[..]/bar-0.0.1/src/lib.rs
[ROOT]/home/.cargo-moved/git/checkouts/baz-[HASH]/[..]/src/lib.rs

"#]])
.run();
}

#[cargo_test]
fn cargo_home_moved_trim_paths_object() {
let (p, new_cargo_home) = cargo_home_moved_trim_paths("object");

p.cargo("build --verbose")
.env("CARGO_HOME", &new_cargo_home)
.with_stderr_data(
str![[r#"
[FRESH] bar v0.0.1
[FRESH] baz v0.0.1 ([ROOTURL]/baz#[..])
[FRESH] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]
.unordered(),
)
.run();

p.process(&p.bin("foo"))
.with_stdout_data(str![[r#"
/cargo/registry/[..]/bar-0.0.1/src/lib.rs
/cargo/git/[..]/src/lib.rs

"#]])
.run();
}

#[cargo_test]
fn cargo_home_moved_trim_paths_all() {
let (p, new_cargo_home) = cargo_home_moved_trim_paths("all");

p.cargo("build --verbose")
.env("CARGO_HOME", &new_cargo_home)
.with_stderr_data(
str![[r#"
[FRESH] bar v0.0.1
[FRESH] baz v0.0.1 ([ROOTURL]/baz#[..])
[FRESH] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]]
.unordered(),
)
.run();

p.process(&p.bin("foo"))
.with_stdout_data(str![[r#"
/cargo/registry/[..]/bar-0.0.1/src/lib.rs
/cargo/git/[..]/src/lib.rs

"#]])
.run();
}

#[cargo_test]
fn cargo_home_moved_trim_paths_object_with_diagnostics() {
Package::new("bar", "0.0.1")
.file("Cargo.toml", &basic_manifest("bar", "0.0.1"))
.file("src/lib.rs", "pub fn f() { let unused = 0; }")
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"

[dependencies]
bar = "0.0.1"

[profile.dev]
trim-paths = "object"
"#,
)
.file("src/lib.rs", "")
.build();

// `-vv` shows warnings of non-local packages.
p.cargo("build -vv").run();

let new_cargo_home = paths::home().join(".cargo-moved");
std::fs::rename(paths::cargo_home(), &new_cargo_home).unwrap();

// The `object` scope doesn't remap diagnostics,
// so cached warning replay still points to old `.cargo` rather than `.cargo-moved`.
p.cargo("build -vv")
.env("CARGO_HOME", &new_cargo_home)
.with_stderr_data(str![[r#"
...
[FRESH] bar v0.0.1
[WARNING] unused variable: `unused`
--> [ROOT]/home/.cargo/registry/src/-[..]/bar-0.0.1/src/lib.rs:1:18
...
Comment on lines +2376 to +2379

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raise the awareness of this:

One caveat is that because the "object" scope doesn't remap diagonstics,
if diagnostics were also cached,
a replay may show the "wrong" cached paths.
The "all" scope doesn't have this issue btw.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a comment pointing out that .cargo is cached and should actually be .cargo-moved but its a trade off we accepted.

Since its not obvious outside of the context of this PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Thanks.

"#]])
.run();
}
Loading