From a7715cb5f30eb5cf1423dda53bb29a3261ea9499 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Wed, 4 Jun 2025 07:34:47 -0400 Subject: [PATCH 1/2] test: rmeta is not reused --- tests/testsuite/freshness.rs | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index a930e81fe2d..4ecf047e24a 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -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#" +[CHECKING] foo v0.0.0 ([ROOT]/foo) +[RUNNING] `rustc [..]` +[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#" +[COMPILING] foo v0.0.0 ([ROOT]/foo) +[RUNNING] `rustc [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} From ac4b1c8c53d3aabfe80a37b0136526524ec76563 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Wed, 4 Jun 2025 07:20:13 -0400 Subject: [PATCH 2/2] feat: maximize 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. --- src/cargo/core/compiler/build_config.rs | 20 +++++++++++++++++++ .../build_runner/compilation_files.rs | 2 +- src/cargo/core/compiler/build_runner/mod.rs | 13 +++++++++++- src/cargo/core/compiler/fingerprint/mod.rs | 2 +- tests/testsuite/artifact_dep.rs | 2 +- tests/testsuite/collisions.rs | 1 - tests/testsuite/freshness.rs | 4 ++-- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index bec2c914bd4..b2a6618a116 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -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. diff --git a/src/cargo/core/compiler/build_runner/compilation_files.rs b/src/cargo/core/compiler/build_runner/compilation_files.rs index 1892e8775bb..6a573b893c7 100644 --- a/src/cargo/core/compiler/build_runner/compilation_files.rs +++ b/src/cargo/core/compiler/build_runner/compilation_files.rs @@ -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 diff --git a/src/cargo/core/compiler/build_runner/mod.rs b/src/cargo/core/compiler/build_runner/mod.rs index 692e36d8d88..c09ba1b89c3 100644 --- a/src/cargo/core/compiler/build_runner/mod.rs +++ b/src/cargo/core/compiler/build_runner/mod.rs @@ -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() { diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs index af7ddf2d2a9..3b9b49c33cf 100644 --- a/src/cargo/core/compiler/fingerprint/mod.rs +++ b/src/cargo/core/compiler/fingerprint/mod.rs @@ -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(), diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index 3da74c19819..9c43d4f1141 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -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); p.cargo("doc -Z bindeps") .masquerade_as_nightly_cargo(&["bindeps"]) diff --git a/tests/testsuite/collisions.rs b/tests/testsuite/collisions.rs index 1f69f3e7f44..7d72762653e 100644 --- a/tests/testsuite/collisions.rs +++ b/tests/testsuite/collisions.rs @@ -404,7 +404,6 @@ fn collision_doc_profile_split() { p.cargo("doc") .with_stderr_data( str![[r#" -[CHECKING] common v1.0.0 [DOCUMENTING] common v1.0.0 [DOCUMENTING] pm v0.1.0 ([ROOT]/foo/pm) [DOCUMENTING] foo v0.1.0 ([ROOT]/foo) diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index 4ecf047e24a..a70ba761750 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -3210,8 +3210,7 @@ fn rmeta_reuse() { p.cargo("check --verbose") .with_stderr_data(str![[r#" -[CHECKING] foo v0.0.0 ([ROOT]/foo) -[RUNNING] `rustc [..]` +[FRESH] foo v0.0.0 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) @@ -3229,6 +3228,7 @@ fn rmeta_reuse() { 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