From 46a9f3c5e3234331a943c3ae8c83a7386af792b3 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 28 Oct 2025 15:55:10 -0700 Subject: [PATCH] Add support for rustdoc mergeable cross-crate info parts This is an unstable feature that we designed to fix several performance problems with the old system: 1. You couldn't easily build crate docs in hermetic environments. This doesn't matter for Cargo, but it was one of the original reasons to implement the feature. 2. We have to build all the doc resources in their final form at every step, instead of delaying slow parts (mostly the search index) until the end and only doing them once. 3. It requires rustdoc to take a lock at the end. This reduces available concurrency for generating docs. --- .../compiler/build_context/target_info.rs | 9 ++ .../build_runner/compilation_files.rs | 7 ++ src/cargo/core/compiler/build_runner/mod.rs | 44 +++++++ src/cargo/core/compiler/layout.rs | 4 + src/cargo/core/compiler/mod.rs | 21 +++- src/cargo/core/features.rs | 2 + tests/testsuite/cargo/z_help/stdout.term.svg | 30 ++--- tests/testsuite/doc.rs | 119 ++++++++++++++++++ 8 files changed, 220 insertions(+), 16 deletions(-) diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 76e387c579c..906efa33727 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -1191,6 +1191,15 @@ impl RustDocFingerprint { }) .filter(|path| path.exists()) .try_for_each(|path| clean_doc(path))?; + if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { + build_runner + .bcx + .all_kinds + .iter() + .map(|kind| build_runner.files().layout(*kind).build_dir().doc_parts()) + .filter(|path| path.exists()) + .try_for_each(|path| clean_doc(&path))?; + } write_fingerprint()?; return Ok(()); diff --git a/src/cargo/core/compiler/build_runner/compilation_files.rs b/src/cargo/core/compiler/build_runner/compilation_files.rs index 623dcbba926..4796733c39f 100644 --- a/src/cargo/core/compiler/build_runner/compilation_files.rs +++ b/src/cargo/core/compiler/build_runner/compilation_files.rs @@ -328,6 +328,13 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> { .build_script(&dir) } + /// Returns the directory where mergeable cross crate info for docs is stored. + pub fn doc_parts_dir(&self, unit: &Unit) -> PathBuf { + assert!(unit.mode.is_doc()); + assert!(self.metas.contains_key(unit)); + self.layout(unit.kind).build_dir().doc_parts().to_path_buf() + } + /// Returns the directory for compiled artifacts files. /// `/path/to/target/{debug,release}/deps/artifact/KIND/PKG-HASH` fn artifact_dir(&self, unit: &Unit) -> PathBuf { diff --git a/src/cargo/core/compiler/build_runner/mod.rs b/src/cargo/core/compiler/build_runner/mod.rs index d1ae513238e..3760969be76 100644 --- a/src/cargo/core/compiler/build_runner/mod.rs +++ b/src/cargo/core/compiler/build_runner/mod.rs @@ -1,6 +1,7 @@ //! [`BuildRunner`] is the mutable state used during the build process. use std::collections::{HashMap, HashSet}; +use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -302,6 +303,49 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { .insert(dir.clone().into_path_buf()); } } + + if self.bcx.build_config.intent.is_doc() + && self.bcx.gctx.cli_unstable().rustdoc_mergeable_info + && let Some(unit) = self + .bcx + .roots + .iter() + .filter(|unit| unit.mode.is_doc()) + .next() + { + let mut rustdoc = self.compilation.rustdoc_process(unit, None)?; + let doc_dir = self.files().out_dir(unit); + let mut include_arg = OsString::from("--include-parts-dir="); + include_arg.push(self.files().doc_parts_dir(&unit)); + rustdoc + .arg("-o") + .arg(&doc_dir) + .arg("--emit=toolchain-shared-resources") + .arg("-Zunstable-options") + .arg("--merge=finalize") + .arg(include_arg); + exec.exec( + &rustdoc, + unit.pkg.package_id(), + &unit.target, + CompileMode::Doc, + // This is always single-threaded, and always gets run, + // so thread delinterleaving isn't needed and neither is + // the output cache. + &mut |line| { + let mut shell = self.bcx.gctx.shell(); + shell.print_ansi_stdout(line.as_bytes())?; + shell.err().write_all(b"\n")?; + Ok(()) + }, + &mut |line| { + let mut shell = self.bcx.gctx.shell(); + shell.print_ansi_stderr(line.as_bytes())?; + shell.err().write_all(b"\n")?; + Ok(()) + }, + )?; + } Ok(self.compilation) } diff --git a/src/cargo/core/compiler/layout.rs b/src/cargo/core/compiler/layout.rs index 8824350b8a5..9216d29e149 100644 --- a/src/cargo/core/compiler/layout.rs +++ b/src/cargo/core/compiler/layout.rs @@ -353,6 +353,10 @@ impl BuildDirLayout { self.build().join(pkg_dir) } } + /// Fetch the doc parts path. + pub fn doc_parts(&self) -> PathBuf { + self.build().join("doc.parts") + } /// Fetch the build script execution path. pub fn build_script_execution(&self, pkg_dir: &str) -> PathBuf { if self.is_new_layout { diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 84093ff0a96..141fd574ced 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -830,8 +830,13 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu if build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo { // toolchain-shared-resources is required for keeping the shared styling resources // invocation-specific is required for keeping the original rustdoc emission - let mut arg = - OsString::from("--emit=toolchain-shared-resources,invocation-specific,dep-info="); + let mut arg = if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { + // toolchain resources are written at the end, at the same time as merging + OsString::from("--emit=invocation-specific,dep-info=") + } else { + // if not using mergeable CCI, everything is written every time + OsString::from("--emit=toolchain-shared-resources,invocation-specific,dep-info=") + }; arg.push(rustdoc_dep_info_loc(build_runner, unit)); rustdoc.arg(arg); @@ -840,6 +845,18 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu } rustdoc.arg("-Zunstable-options"); + } else if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { + // toolchain resources are written at the end, at the same time as merging + rustdoc.arg("--emit=invocation-specific"); + rustdoc.arg("-Zunstable-options"); + } + + if build_runner.bcx.gctx.cli_unstable().rustdoc_mergeable_info { + // write out mergeable data to be imported + rustdoc.arg("--merge=none"); + let mut arg = OsString::from("--parts-out-dir="); + arg.push(build_runner.files().doc_parts_dir(&unit)); + rustdoc.arg(arg); } if let Some(trim_paths) = unit.profile.trim_paths.as_ref() { diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index f9f13dc2951..36bb8414296 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -884,6 +884,7 @@ unstable_cli_options!( rustc_unicode: bool = ("Enable `rustc`'s unicode error format in Cargo's error messages"), rustdoc_depinfo: bool = ("Use dep-info files in rustdoc rebuild detection"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), + rustdoc_mergeable_info: bool = ("Use rustdoc mergeable cross-crate-info files"), rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"), sbom: bool = ("Enable the `sbom` option in build config in .cargo/config.toml file"), script: bool = ("Enable support for single-file, `.rs` packages"), @@ -1415,6 +1416,7 @@ impl CliUnstable { "rustc-unicode" => self.rustc_unicode = parse_empty(k, v)?, "rustdoc-depinfo" => self.rustdoc_depinfo = parse_empty(k, v)?, "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, + "rustdoc-mergeable-info" => self.rustdoc_mergeable_info = parse_empty(k, v)?, "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?, "sbom" => self.sbom = parse_empty(k, v)?, "section-timings" => self.section_timings = parse_empty(k, v)?, diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index f3354ac179b..e1dddbc6cec 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,4 +1,4 @@ - +