Skip to content

[WIP] feat: maximize artifact reuse between different modes #15627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
20 changes: 20 additions & 0 deletions src/cargo/core/compiler/build_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,26 @@ impl CompileMode {
pub fn generates_executable(self) -> bool {
matches!(self, CompileMode::Test | CompileMode::Build)
}

/// Maximizes artifact reuse between different modes.
///
/// As of this writing, it aims to reuse `rmeta` between build and check modes.
/// (one emits rmeta + rlib, and the other emits rmeta)
///
/// This has a caveat that `DefId` in rmeta files might not match between rustc
/// invocations with different `--emits` values, and that might lead to rustc
/// rejecting the input rmeta. To avoid failures in rustc, this is currently
/// guarded by fingerprint to ensure all output files present, if not, rerun.
/// This make running `check` after `build` safe: `build`'s rmeta files might
/// have more information and rustc is correct on rejection.
pub fn for_reuse(self) -> CompileMode {
match self {
// We might want to reuse `Test` and `Check { test: true }`,
// but those are usually local so dont bother it
CompileMode::Build | CompileMode::Check { test: false } => CompileMode::Build,
m => m,
}
}
}

/// Represents the high-level operation requested by the user.
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/compiler/build_runner/compilation_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ fn compute_metadata(
// `panic=abort` and `panic=unwind` artifacts, additionally with various
// settings like debuginfo and whatnot.
unit.profile.hash(&mut shared_hasher);
unit.mode.hash(&mut shared_hasher);
unit.mode.for_reuse().hash(&mut shared_hasher);
build_runner.lto[unit].hash(&mut shared_hasher);

// Artifacts compiled for the host should have a different
Expand Down
13 changes: 12 additions & 1 deletion src/cargo/core/compiler/build_runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,12 +600,23 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
}
for output in self.outputs(unit)?.iter() {
if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) {
let is_check = |u: &Unit| matches!(u.mode, CompileMode::Check { test: false });
let is_build = |u: &Unit| matches!(u.mode, CompileMode::Build);
let is_build_check_reuse =
|a, b| (is_check(a) && is_build(b)) || (is_check(b) && is_build(a));

if unit.mode.is_doc() {
// See https://github.com/rust-lang/rust/issues/56169
// and https://github.com/rust-lang/rust/issues/61378
report_collision(unit, other_unit, &output.path, rustdoc_suggestion)?;
} else {
report_collision(unit, other_unit, &output.path, suggestion)?;
if is_build_check_reuse(unit, other_unit) {
tracing::debug!(
"reusing artifacts between {unit:?} and {other_unit:?}"
);
} else {
report_collision(unit, other_unit, &output.path, suggestion)?;
}
}
}
if let Some(hardlink) = output.hardlink.as_ref() {
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/compiler/fingerprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ fn calculate_normal(

let profile_hash = util::hash_u64((
&unit.profile,
unit.mode,
unit.mode.for_reuse(),
build_runner.bcx.extra_args_for(unit),
build_runner.lto[unit],
unit.pkg.manifest().lint_rustflags(),
Expand Down
2 changes: 1 addition & 1 deletion tests/testsuite/artifact_dep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2416,7 +2416,7 @@ fn doc_lib_true() {

// Verify that it emits rmeta for the bin and lib dependency.
assert_eq!(p.glob("target/debug/artifact/*.rlib").count(), 0);
assert_eq!(p.glob("target/debug/deps/libbar-*.rmeta").count(), 2);
assert_eq!(p.glob("target/debug/deps/libbar-*.rmeta").count(), 1);
Copy link
Member Author

Choose a reason for hiding this comment

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

Artifact lib build generated rmeta first, so doc build can skip checking dependencies.


p.cargo("doc -Z bindeps")
.masquerade_as_nightly_cargo(&["bindeps"])
Expand Down
1 change: 0 additions & 1 deletion tests/testsuite/collisions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,6 @@ fn collision_doc_profile_split() {
p.cargo("doc")
.with_stderr_data(
str![[r#"
[CHECKING] common v1.0.0
Copy link
Member Author

Choose a reason for hiding this comment

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

proc-macro build has generated rmeta first, so skipping checking.

[DOCUMENTING] common v1.0.0
[DOCUMENTING] pm v0.1.0 ([ROOT]/foo/pm)
[DOCUMENTING] foo v0.1.0 ([ROOT]/foo)
Expand Down
52 changes: 52 additions & 0 deletions tests/testsuite/freshness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3184,3 +3184,55 @@ fn use_mtime_cache_in_cargo_home() {
"#]])
.run();
}

#[cargo_test]
fn rmeta_reuse() {
// Currently support only one direction `build` -> `check`/`doc`
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2015"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("build")
.with_stderr_data(str![[r#"
[COMPILING] foo v0.0.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();

p.cargo("check --verbose")
.with_stderr_data(str![[r#"
[FRESH] foo v0.0.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();

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

p.cargo("check")
.with_stderr_data(str![[r#"
[CHECKING] foo v0.0.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();

p.cargo("build --verbose")
.with_stderr_data(str![[r#"
[DIRTY] foo v0.0.0 ([ROOT]/foo): couldn't read metadata for file `target/debug/deps/libfoo-[HASH].rlib`
[COMPILING] foo v0.0.0 ([ROOT]/foo)
[RUNNING] `rustc [..]`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

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