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 a930e81fe2d..a70ba761750 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#" +[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(); +}