From 94d3fd16af23c12a8d9d779dbfdb790b7b13384f Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Tue, 28 Jul 2026 18:02:35 +0000 Subject: [PATCH 01/11] bootstrap: Enable rustdoc mergeable CCI for std and internal docs This feature is unstable but will be stabilized soon, and this is a good way of dogfooding it to make sure it works properly. It should have no effect on the generated docs, but it provides a significant speedup. For example, I measure a 3x speedup locally (3m 11s -> 1m 1s) for `x doc src/tools` -- note that this is with the latest rustdoc perf improvements (PR 159854). --- src/bootstrap/src/core/builder/cargo.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 9eff50772a8f2..4d4984fd9b23a 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -724,6 +724,9 @@ impl Builder<'_> { } if cmd_kind == Kind::Doc { + // Will be stabilized soon -> let's dogfood it. + // No effect on doc output but massive doc-generation time improvements. + cargo.arg("-Zrustdoc-mergeable-info"); let my_out = match mode { // This is the intended out directory for compiler documentation. Mode::Rustc | Mode::ToolRustcPrivate | Mode::ToolBootstrap | Mode::ToolTarget => { From e4cdb144f57676f4acbd4d4e78e43049f0aba20b Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 22 Aug 2026 12:59:48 -0700 Subject: [PATCH 02/11] bootstrap: merge compiler docs as separate step As discussed in the [old version of this PR][], we can build the original version of the docs in separate build directories, and then merge them by calling rustdoc directly. This way, the crates don't invalidate each other's build caches, and we don't have to mess with symlinks or copying things around. [old version of this PR]: https://github.com/rust-lang/rust/pull/160098#issuecomment-5379072327 --- src/bootstrap/src/core/build_steps/doc.rs | 293 ++++++++++++++++------ src/bootstrap/src/core/builder/mod.rs | 1 + src/bootstrap/src/core/builder/tests.rs | 11 +- 3 files changed, 217 insertions(+), 88 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 81c112db5eee5..a43700a44256d 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -20,9 +20,9 @@ use crate::core::builder::{ crate_description, }; use crate::core::compiler::Compiler; -use crate::core::config::{Config, TargetSelection}; +use crate::core::config::TargetSelection; use crate::core::session::{FileType, Mode}; -use crate::utils::helpers::{submodule_path_of, symlink_dir, t, up_to_date}; +use crate::utils::helpers::{submodule_path_of, t, up_to_date}; macro_rules! book { ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => { @@ -871,6 +871,170 @@ pub fn prepare_doc_compiler( build_compiler } +/// Generate the combined compiler docs for a given toolchain. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct CompilerDoc { + build_compiler: Compiler, + target: TargetSelection, + stage: u32, +} + +impl CompilerDoc { + /// Document `stage` compiler for the given `target`. + pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self { + let build_compiler = prepare_doc_compiler(builder, target, stage); + Self { build_compiler, target, stage } + } +} + +impl CommandLineStep for CompilerDoc { + type Output = (); + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("compiler-doc") + } + + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.compiler_docs + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(CompilerDoc::for_stage(run.builder, run.builder.top_stage, run.target)); + } + + /// Generates compiler documentation. + /// + /// This will generate all documentation for compiler and dependencies. + /// Compiler documentation is distributed separately, so we make sure + /// we do not merge it with the other documentation from std, test and + /// proc_macros. This is largely just a wrapper around `cargo doc`. + fn run(self, builder: &Builder<'_>) { + let CompilerDoc { target, build_compiler, stage } = self; + + // This is the intended out directory for compiler documentation. + let out = builder.compiler_doc_out(target); + t!(fs::create_dir_all(&out)); + + let _guard = + builder.msg(Kind::Doc, format!("compiler-doc"), Mode::Rustc, build_compiler, target); + + let mut cmd = builder.rustdoc_cmd(build_compiler); + + cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(&out); + + if !builder.config.docs_minification { + cmd.arg("--disable-minification"); + } + + #[derive(serde_derive::Deserialize)] + struct FingerprintData { + doc_parts: Vec, + } + + let rustc_stage = Rustc::for_stage(builder, stage, target); + builder.ensure(rustc_stage.clone()); + let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target); + // Cargo puts proc macros in `target/doc` even if you pass `--target` + // explicitly (https://github.com/rust-lang/cargo/issues/7677). + let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc); + // Copy crate docs into place. + for krate in &*rustc_stage.crates { + let dir_name = krate.replace('-', "_"); + let crate_doc_dir = out_dir.join("doc").join(&dir_name); + let proc_macro_doc_dir = proc_macro_out_dir.join("doc").join(&dir_name); + let doc_out = out.join(&dir_name); + t!(fs::create_dir_all(&doc_out)); + if proc_macro_doc_dir.exists() { + builder.cp_link_r(&proc_macro_doc_dir, &doc_out); + } else if crate_doc_dir.exists() { + builder.cp_link_r(&crate_doc_dir, &doc_out); + } else if !builder.config.dry_run() { + panic!("no docs found for {krate} in {}", crate_doc_dir.display()); + } + // Making sure the directory exists and is not empty. + if !builder.config.dry_run() { + assert!(doc_out.exists(), "{}", doc_out.display()); + assert!( + doc_out.read_dir().expect(&dir_name).next().is_some(), + "{}", + doc_out.display() + ); + } + } + if !builder.config.dry_run() { + let fingerprint_rustc = + t!(std::fs::read_to_string(&out_dir.join(".rustdoc_fingerprint.json"))); + let fingerprint_rustc: FingerprintData = t!(serde_json::from_str(&fingerprint_rustc)); + for part in fingerprint_rustc.doc_parts.iter() { + cmd.arg("--read-doc-meta-dir").arg(out_dir.join(part).parent().unwrap()); + } + } + + macro_rules! merge_tool_doc { + ($tool: ident, $builder: ident, $target: ident) => {{ + let tool_stage = $tool::new($builder, $target); + builder.ensure(tool_stage.clone()); + let out_dir = builder.stage_out(build_compiler, tool_stage.mode).join(target); + let proc_macro_out_dir = builder.stage_out(build_compiler, tool_stage.mode); + for krate in $tool::crates() { + let dir_name = krate.replace('-', "_"); + let crate_doc_dir = out_dir.join("doc").join(&dir_name); + let proc_macro_doc_dir = proc_macro_out_dir.join("doc").join(&dir_name); + let doc_out = out.join(&dir_name); + t!(fs::create_dir_all(&doc_out)); + if proc_macro_doc_dir.exists() { + builder.cp_link_r(&proc_macro_doc_dir, &doc_out); + } else if crate_doc_dir.exists() { + builder.cp_link_r(&crate_doc_dir, &doc_out); + } else if !builder.config.dry_run() { + panic!("no docs found for {krate} in {}", crate_doc_dir.display()); + } + // Making sure the directory exists and is not empty. + if !builder.config.dry_run() { + assert!(doc_out.exists(), "{}", doc_out.display()); + assert!( + doc_out.read_dir().expect(&dir_name).next().is_some(), + "{}", + doc_out.display() + ); + } + } + }}; + } + + merge_tool_doc!(BuildHelper, builder, target); + merge_tool_doc!(Rustdoc, builder, target); + merge_tool_doc!(Rustfmt, builder, target); + merge_tool_doc!(Clippy, builder, target); + merge_tool_doc!(Miri, builder, target); + merge_tool_doc!(Cargo, builder, target); + merge_tool_doc!(Tidy, builder, target); + merge_tool_doc!(Bootstrap, builder, target); + merge_tool_doc!(RunMakeSupport, builder, target); + merge_tool_doc!(Compiletest, builder, target); + + if !builder.config.dry_run() { + let out_dir_tool = builder.stage_out(build_compiler, Mode::ToolTarget).join(target); + let fingerprint_tool = + t!(std::fs::read_to_string(&out_dir_tool.join(".rustdoc_fingerprint.json"))); + let fingerprint_tool: FingerprintData = t!(serde_json::from_str(&fingerprint_tool)); + for part in fingerprint_tool.doc_parts.iter() { + cmd.arg("--read-doc-meta-dir").arg(out_dir_tool.join(part).parent().unwrap()); + } + } + + cmd.run(builder); + + // Handle `--open`. + builder.open_in_browser(out.join("index.html")); + } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc("compiler-doc", self.target).built_by(self.build_compiler)) + } +} + /// Document the compiler for the given `target` using rustdoc from `build_compiler`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustc { @@ -925,10 +1089,6 @@ impl CommandLineStep for Rustc { fn run(self, builder: &Builder<'_>) { let target = self.target; - // This is the intended out directory for compiler documentation. - let out = builder.compiler_doc_out(target); - t!(fs::create_dir_all(&out)); - // Build the standard library, so that proc-macros can use it. // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.) let build_compiler = self.build_compiler; @@ -977,8 +1137,6 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("--extern-html-root-url"); cargo.rustdocflag("ena=https://docs.rs/ena/latest/"); - let mut to_open = None; - let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc"); for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses @@ -987,41 +1145,22 @@ impl CommandLineStep for Rustc { let dir_name = krate.replace('-', "_"); t!(fs::create_dir_all(out_dir.join(&*dir_name))); cargo.arg("-p").arg(krate); - if to_open.is_none() { - to_open = Some(dir_name); - } } - // This uses a shared directory so that librustdoc documentation gets - // correctly built and merged with the rustc documentation. - // - // This is needed because rustdoc is built in a different directory from - // rustc. rustdoc needs to be able to see everything, for example when - // merging the search index, or generating local (relative) links. - symlink_dir_force(&builder.config, &out, &out_dir); - // Cargo puts proc macros in `target/doc` even if you pass `--target` - // explicitly (https://github.com/rust-lang/cargo/issues/7677). - let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc"); - symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); - cargo.into_cmd().run(builder); - if !builder.config.dry_run() { - // Sanity check on linked compiler crates - for krate in &*self.crates { - let dir_name = krate.replace('-', "_"); - // Making sure the directory exists and is not empty. - assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some()); - } - } - - if builder.paths.iter().any(|path| path.ends_with("compiler")) { - // For `x.py doc compiler --open`, open `rustc_middle` by default. - let index = out.join("rustc_middle").join("index.html"); - builder.open_in_browser(index); - } else if let Some(krate) = to_open { - // Let's open the first crate documentation page: - let index = out.join(krate).join("index.html"); + // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md` + // with no particular explicit doc requested (e.g. library/core). + if builder.was_invoked_explicitly::(Kind::Doc) { + let index = if builder.paths.iter().any(|path| path.ends_with("compiler")) { + // For `x.py doc compiler --open`, open `rustc_middle` by default. + out_dir.join("rustc_middle").join("index.html") + } else if let Some(krate) = self.crates.first() { + // Let's open the first crate documentation page: + out_dir.join(krate).join("index.html") + } else { + out_dir + }; builder.open_in_browser(index); } } @@ -1048,40 +1187,48 @@ macro_rules! tool_doc { target: TargetSelection, } - impl CommandLineStep for $tool { - type Output = (); - const IS_HOST: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path($path) - } - - fn is_default_step(builder: &Builder<'_>) -> bool { - builder.config.compiler_docs - } - - fn make_run(run: RunConfig<'_>) { - let target = run.target; + impl $tool { + fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool { + let target = target; let build_compiler = match $mode { Mode::ToolRustcPrivate => { // Rustdoc needs the rustc sysroot available to build. - let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target); + let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target); // Build rustc docs so that we generate relative links. - run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target)); + builder.ensure(Rustc::from_build_compiler(builder, compilers.build_compiler(), target)); compilers.build_compiler() } Mode::ToolTarget => { // when shipping multiple docs together in one folder, // they all need to use the same rustdoc version - prepare_doc_compiler(run.builder, run.builder.host_target, run.builder.top_stage) + prepare_doc_compiler(builder, builder.host_target, builder.top_stage) } _ => { panic!("Unexpected tool mode for documenting: {:?}", $mode); } }; + $tool { build_compiler, mode: $mode, target } + } + fn crates() -> &'static [&'static str] { + &$($crates)?[..] + } + } + + impl CommandLineStep for $tool { + type Output = (); + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path($path) + } - run.builder.ensure($tool { build_compiler, mode: $mode, target }); + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.compiler_docs + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure($tool::new(run.builder, run.target)); } /// Generates documentation for a tool. @@ -1142,16 +1289,12 @@ macro_rules! tool_doc { cargo.rustdocflag("--generate-link-to-definition"); let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc"); + let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc"); $(for krate in $crates { let dir_name = krate.replace("-", "_"); t!(fs::create_dir_all(out_dir.join(&*dir_name))); })? - // Symlink compiler docs to the output directory of rustdoc documentation. - symlink_dir_force(&builder.config, &out, &out_dir); - let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc"); - symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); - let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target); cargo.into_cmd().run(builder); @@ -1160,7 +1303,11 @@ macro_rules! tool_doc { $(for krate in $crates { let dir_name = krate.replace("-", "_"); // Making sure the directory exists and is not empty. - assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some()); + let doc_out = out_dir.join(&*dir_name); + let proc_macro_doc_out = proc_macro_out_dir.join(&*dir_name); + let dir = if proc_macro_doc_out.exists() { proc_macro_doc_out } else { doc_out }; + assert!(dir.exists(), "{}", dir.display()); + assert!(dir.read_dir().expect(&dir_name).next().is_some()); })? } } @@ -1344,26 +1491,6 @@ impl CommandLineStep for UnstableBookGen { } } -fn symlink_dir_force(config: &Config, original: &Path, link: &Path) { - if config.dry_run() { - return; - } - if let Ok(m) = fs::symlink_metadata(link) { - if m.file_type().is_dir() { - t!(fs::remove_dir_all(link)); - } else { - // handle directory junctions on windows by falling back to - // `remove_dir`. - t!(fs::remove_file(link).or_else(|_| fs::remove_dir(link))); - } - } - - t!( - symlink_dir(config, original, link), - format!("failed to create link from {} -> {}", link.display(), original.display()) - ); -} - /// Builds the Rust compiler book. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RustcBook { diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 966279c293808..c12085e093faf 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -960,6 +960,7 @@ impl<'a> Builder<'a> { doc::CargoBook, doc::Clippy, doc::ClippyBook, + doc::CompilerDoc, doc::Miri, doc::EmbeddedBook, doc::EditionGuide, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1f08ee9c11864..f72b4943a177a 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1026,16 +1026,17 @@ mod snapshot { [doc] cargo (book) [doc] rustc 1 -> Clippy 2 [doc] clippy (book) + [doc] rustc 1 -> BuildHelper 2 [doc] rustc 1 -> Miri 2 - [doc] embedded-book (book) - [doc] edition-guide (book) - [doc] style-guide (book) [doc] rustc 1 -> Tidy 2 [doc] rustc 1 -> Bootstrap 2 - [doc] rustc 1 -> releases 2 [doc] rustc 1 -> RunMakeSupport 2 - [doc] rustc 1 -> BuildHelper 2 [doc] rustc 1 -> Compiletest 2 + [doc] rustc 1 -> compiler-doc 2 + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 " ); From ebdadd7d4e26e09db917795ea010dbc9f3a96c91 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 22 Aug 2026 17:35:37 -0700 Subject: [PATCH 03/11] bootstrap: remove the now-unneeded `-Zskip-rustdoc-fingerprint` arg --- src/bootstrap/src/core/build_steps/doc.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index a43700a44256d..6a81400de01c5 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -840,7 +840,6 @@ fn doc_std( .arg("--no-deps") .arg("--target-dir") .arg(&*target_dir.to_string_lossy()) - .arg("-Zskip-rustdoc-fingerprint") .arg("-Zrustdoc-map") .rustdocflag("--extern-html-root-url") .rustdocflag("std_detect=https://docs.rs/std_detect/latest/") @@ -916,8 +915,7 @@ impl CommandLineStep for CompilerDoc { let out = builder.compiler_doc_out(target); t!(fs::create_dir_all(&out)); - let _guard = - builder.msg(Kind::Doc, format!("compiler-doc"), Mode::Rustc, build_compiler, target); + let _guard = builder.msg(Kind::Doc, "compiler-doc", Mode::Rustc, build_compiler, target); let mut cmd = builder.rustdoc_cmd(build_compiler); @@ -964,7 +962,7 @@ impl CommandLineStep for CompilerDoc { } if !builder.config.dry_run() { let fingerprint_rustc = - t!(std::fs::read_to_string(&out_dir.join(".rustdoc_fingerprint.json"))); + t!(std::fs::read_to_string(out_dir.join(".rustdoc_fingerprint.json"))); let fingerprint_rustc: FingerprintData = t!(serde_json::from_str(&fingerprint_rustc)); for part in fingerprint_rustc.doc_parts.iter() { cmd.arg("--read-doc-meta-dir").arg(out_dir.join(part).parent().unwrap()); @@ -1017,7 +1015,7 @@ impl CommandLineStep for CompilerDoc { if !builder.config.dry_run() { let out_dir_tool = builder.stage_out(build_compiler, Mode::ToolTarget).join(target); let fingerprint_tool = - t!(std::fs::read_to_string(&out_dir_tool.join(".rustdoc_fingerprint.json"))); + t!(std::fs::read_to_string(out_dir_tool.join(".rustdoc_fingerprint.json"))); let fingerprint_tool: FingerprintData = t!(serde_json::from_str(&fingerprint_tool)); for part in fingerprint_tool.doc_parts.iter() { cmd.arg("--read-doc-meta-dir").arg(out_dir_tool.join(part).parent().unwrap()); @@ -1125,7 +1123,6 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("--generate-macro-expansion"); compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); - cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. // Do link to dependencies on `docs.rs` however using `rustdoc-map`. @@ -1189,7 +1186,6 @@ macro_rules! tool_doc { impl $tool { fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool { - let target = target; let build_compiler = match $mode { Mode::ToolRustcPrivate => { // Rustdoc needs the rustc sysroot available to build. @@ -1269,7 +1265,6 @@ macro_rules! tool_doc { cargo.allow_features(allow_features); } - cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. cargo.arg("--no-deps"); From fdf2f353ee2edda7a9b53305b857d9963f853eab Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 25 Aug 2026 22:01:23 -0700 Subject: [PATCH 04/11] doc: merge macro crates into the target docs This symlinks the docs into place, and takes care of merging the CCI metadata so that local builds include macros in the list of items. --- src/bootstrap/src/core/build_steps/doc.rs | 136 ++++++++++++++-------- 1 file changed, 89 insertions(+), 47 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 6a81400de01c5..55386548c5694 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -22,7 +22,7 @@ use crate::core::builder::{ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; use crate::core::session::{FileType, Mode}; -use crate::utils::helpers::{submodule_path_of, t, up_to_date}; +use crate::utils::helpers::{submodule_path_of, symlink_dir, t, up_to_date}; macro_rules! book { ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => { @@ -870,6 +870,40 @@ pub fn prepare_doc_compiler( build_compiler } +fn merge_rustdoc_cci_parts_from_fingerprints( + builder: &Builder<'_>, + build_compiler: Compiler, + fingerprints: impl IntoIterator>, + out_dir: impl AsRef, +) { + let mut cmd = builder.rustdoc_cmd(build_compiler); + + cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(&out_dir.as_ref()); + + if !builder.config.docs_minification { + cmd.arg("--disable-minification"); + } + + #[derive(serde_derive::Deserialize)] + struct FingerprintData { + doc_parts: Vec, + } + + for fingerprint in fingerprints { + let fingerprint_file = fingerprint.as_ref().join(".rustdoc_fingerprint.json"); + if fingerprint_file.exists() { + let fingerprint_data = t!(std::fs::read_to_string(fingerprint_file)); + let fingerprint_data: FingerprintData = t!(serde_json::from_str(&fingerprint_data)); + for part in fingerprint_data.doc_parts.iter() { + cmd.arg("--read-doc-meta-dir") + .arg(fingerprint.as_ref().join(part).parent().unwrap()); + } + } + } + + cmd.run(builder); +} + /// Generate the combined compiler docs for a given toolchain. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct CompilerDoc { @@ -913,39 +947,19 @@ impl CommandLineStep for CompilerDoc { // This is the intended out directory for compiler documentation. let out = builder.compiler_doc_out(target); - t!(fs::create_dir_all(&out)); let _guard = builder.msg(Kind::Doc, "compiler-doc", Mode::Rustc, build_compiler, target); - let mut cmd = builder.rustdoc_cmd(build_compiler); - - cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(&out); - - if !builder.config.docs_minification { - cmd.arg("--disable-minification"); - } - - #[derive(serde_derive::Deserialize)] - struct FingerprintData { - doc_parts: Vec, - } - let rustc_stage = Rustc::for_stage(builder, stage, target); builder.ensure(rustc_stage.clone()); let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target); - // Cargo puts proc macros in `target/doc` even if you pass `--target` - // explicitly (https://github.com/rust-lang/cargo/issues/7677). - let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc); // Copy crate docs into place. for krate in &*rustc_stage.crates { let dir_name = krate.replace('-', "_"); let crate_doc_dir = out_dir.join("doc").join(&dir_name); - let proc_macro_doc_dir = proc_macro_out_dir.join("doc").join(&dir_name); let doc_out = out.join(&dir_name); t!(fs::create_dir_all(&doc_out)); - if proc_macro_doc_dir.exists() { - builder.cp_link_r(&proc_macro_doc_dir, &doc_out); - } else if crate_doc_dir.exists() { + if crate_doc_dir.exists() { builder.cp_link_r(&crate_doc_dir, &doc_out); } else if !builder.config.dry_run() { panic!("no docs found for {krate} in {}", crate_doc_dir.display()); @@ -960,30 +974,18 @@ impl CommandLineStep for CompilerDoc { ); } } - if !builder.config.dry_run() { - let fingerprint_rustc = - t!(std::fs::read_to_string(out_dir.join(".rustdoc_fingerprint.json"))); - let fingerprint_rustc: FingerprintData = t!(serde_json::from_str(&fingerprint_rustc)); - for part in fingerprint_rustc.doc_parts.iter() { - cmd.arg("--read-doc-meta-dir").arg(out_dir.join(part).parent().unwrap()); - } - } macro_rules! merge_tool_doc { ($tool: ident, $builder: ident, $target: ident) => {{ let tool_stage = $tool::new($builder, $target); builder.ensure(tool_stage.clone()); let out_dir = builder.stage_out(build_compiler, tool_stage.mode).join(target); - let proc_macro_out_dir = builder.stage_out(build_compiler, tool_stage.mode); for krate in $tool::crates() { let dir_name = krate.replace('-', "_"); let crate_doc_dir = out_dir.join("doc").join(&dir_name); - let proc_macro_doc_dir = proc_macro_out_dir.join("doc").join(&dir_name); let doc_out = out.join(&dir_name); t!(fs::create_dir_all(&doc_out)); - if proc_macro_doc_dir.exists() { - builder.cp_link_r(&proc_macro_doc_dir, &doc_out); - } else if crate_doc_dir.exists() { + if crate_doc_dir.exists() { builder.cp_link_r(&crate_doc_dir, &doc_out); } else if !builder.config.dry_run() { panic!("no docs found for {krate} in {}", crate_doc_dir.display()); @@ -1013,17 +1015,19 @@ impl CommandLineStep for CompilerDoc { merge_tool_doc!(Compiletest, builder, target); if !builder.config.dry_run() { - let out_dir_tool = builder.stage_out(build_compiler, Mode::ToolTarget).join(target); - let fingerprint_tool = - t!(std::fs::read_to_string(out_dir_tool.join(".rustdoc_fingerprint.json"))); - let fingerprint_tool: FingerprintData = t!(serde_json::from_str(&fingerprint_tool)); - for part in fingerprint_tool.doc_parts.iter() { - cmd.arg("--read-doc-meta-dir").arg(out_dir_tool.join(part).parent().unwrap()); - } + merge_rustdoc_cci_parts_from_fingerprints( + builder, + build_compiler, + [ + builder.stage_out(build_compiler, Mode::Rustc).join(target), + builder.stage_out(build_compiler, Mode::ToolTarget).join(target), + builder.stage_out(build_compiler, Mode::Rustc), + builder.stage_out(build_compiler, Mode::ToolTarget), + ], + &out, + ) } - cmd.run(builder); - // Handle `--open`. builder.open_in_browser(out.join("index.html")); } @@ -1135,6 +1139,7 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("ena=https://docs.rs/ena/latest/"); let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc"); + let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc"); for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses // relative links. @@ -1146,6 +1151,38 @@ impl CommandLineStep for Rustc { cargo.into_cmd().run(builder); + if !builder.config.dry_run() { + // Sanity check on linked doc directories + for krate in &*self.crates { + let dir_name = krate.replace("-", "_"); + // Making sure the directory exists and is not empty. + let doc_out = out_dir.join(&*dir_name); + let proc_macro_doc_out = proc_macro_out_dir.join(&*dir_name); + if proc_macro_doc_out.exists() + && doc_out.read_dir().expect(&dir_name).next().is_none() + { + // Cargo puts proc macros in `target/doc` even if you pass `--target` + // explicitly (https://github.com/rust-lang/cargo/issues/7677). + t!(fs::remove_dir(&doc_out)); + t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); + }; + assert!(doc_out.exists(), "{}", doc_out.display()); + assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); + } + } + + if !builder.config.dry_run() { + merge_rustdoc_cci_parts_from_fingerprints( + builder, + build_compiler, + [ + builder.stage_out(build_compiler, Mode::Rustc).join(target), + builder.stage_out(build_compiler, Mode::Rustc), + ], + &out_dir, + ) + } + // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md` // with no particular explicit doc requested (e.g. library/core). if builder.was_invoked_explicitly::(Kind::Doc) { @@ -1300,9 +1337,14 @@ macro_rules! tool_doc { // Making sure the directory exists and is not empty. let doc_out = out_dir.join(&*dir_name); let proc_macro_doc_out = proc_macro_out_dir.join(&*dir_name); - let dir = if proc_macro_doc_out.exists() { proc_macro_doc_out } else { doc_out }; - assert!(dir.exists(), "{}", dir.display()); - assert!(dir.read_dir().expect(&dir_name).next().is_some()); + if proc_macro_doc_out.exists() && doc_out.read_dir().expect(&dir_name).next().is_none() { + // Cargo puts proc macros in `target/doc` even if you pass `--target` + // explicitly (https://github.com/rust-lang/cargo/issues/7677). + t!(fs::remove_dir(&doc_out)); + t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); + }; + assert!(doc_out.exists(), "{}", doc_out.display()); + assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); })? } } From 5fc8dca9e8bb1db3414c94b8cc81750497e5ddbd Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 25 Aug 2026 22:31:20 -0700 Subject: [PATCH 05/11] bootstrap: merge `src` directories This was a feature I forgot about that generates crate-specific HTML, and thus needs merged. Like with docs, source pages are named after the crate they come from, so they can be merged by copying the directory. --- src/bootstrap/src/core/build_steps/doc.rs | 30 ++++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 55386548c5694..a96ff689aef1c 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -959,11 +959,11 @@ impl CommandLineStep for CompilerDoc { let crate_doc_dir = out_dir.join("doc").join(&dir_name); let doc_out = out.join(&dir_name); t!(fs::create_dir_all(&doc_out)); - if crate_doc_dir.exists() { - builder.cp_link_r(&crate_doc_dir, &doc_out); - } else if !builder.config.dry_run() { - panic!("no docs found for {krate} in {}", crate_doc_dir.display()); - } + builder.cp_link_r(&crate_doc_dir, &doc_out); + let crate_src_dir = out_dir.join("doc").join("src").join(&dir_name); + let src_out = out.join("src").join(&dir_name); + t!(fs::create_dir_all(&src_out)); + builder.cp_link_r(&crate_src_dir, &src_out); // Making sure the directory exists and is not empty. if !builder.config.dry_run() { assert!(doc_out.exists(), "{}", doc_out.display()); @@ -985,11 +985,11 @@ impl CommandLineStep for CompilerDoc { let crate_doc_dir = out_dir.join("doc").join(&dir_name); let doc_out = out.join(&dir_name); t!(fs::create_dir_all(&doc_out)); - if crate_doc_dir.exists() { - builder.cp_link_r(&crate_doc_dir, &doc_out); - } else if !builder.config.dry_run() { - panic!("no docs found for {krate} in {}", crate_doc_dir.display()); - } + builder.cp_link_r(&crate_doc_dir, &doc_out); + let crate_src_dir = out_dir.join("doc").join("src").join(&dir_name); + let src_out = out.join("src").join(&dir_name); + t!(fs::create_dir_all(&src_out)); + builder.cp_link_r(&crate_src_dir, &src_out); // Making sure the directory exists and is not empty. if !builder.config.dry_run() { assert!(doc_out.exists(), "{}", doc_out.display()); @@ -1165,7 +1165,10 @@ impl CommandLineStep for Rustc { // explicitly (https://github.com/rust-lang/cargo/issues/7677). t!(fs::remove_dir(&doc_out)); t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); - }; + let src_out = out_dir.join("src").join(&*dir_name); + let proc_macro_src_out = proc_macro_out_dir.join("src").join(&*dir_name); + t!(symlink_dir(&builder.config, &proc_macro_src_out, &src_out)); + } assert!(doc_out.exists(), "{}", doc_out.display()); assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); } @@ -1342,7 +1345,10 @@ macro_rules! tool_doc { // explicitly (https://github.com/rust-lang/cargo/issues/7677). t!(fs::remove_dir(&doc_out)); t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); - }; + let src_out = out_dir.join("src").join(&*dir_name); + let proc_macro_src_out = proc_macro_out_dir.join("src").join(&*dir_name); + t!(symlink_dir(&builder.config, &proc_macro_src_out, &src_out)); + } assert!(doc_out.exists(), "{}", doc_out.display()); assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); })? From 4564f743a0d18a056310da22af77449839e96060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 25 Aug 2026 15:39:17 +0200 Subject: [PATCH 06/11] Rename the combined compiler doc step to `compiler-with-tools` and add some snapshot tests --- src/bootstrap/src/core/build_steps/doc.rs | 39 ++++++++++++++--------- src/bootstrap/src/core/builder/mod.rs | 2 +- src/bootstrap/src/core/builder/tests.rs | 25 +++++++++++++++ src/bootstrap/src/core/session.rs | 2 +- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index a96ff689aef1c..4df9af858064b 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -905,14 +905,24 @@ fn merge_rustdoc_cci_parts_from_fingerprints( } /// Generate the combined compiler docs for a given toolchain. +/// This contains both the compiler docs, docs of rustc_private tools (miri, clippy, etc.), cargo +/// and also some bootstrap related tools (bootstrap itself, compiletest, tidy, etc.). +/// +/// It gets hosted at https://doc.rust-lang.org/nightly/nightly-rustc/index.html. +/// +/// Compiler documentation is distributed separately, so we make sure +/// we do not merge it with the other documentation from std, test and +/// proc_macros. This is largely just a wrapper around `cargo doc`. +/// +/// Returns a path to a directory with the generated documentation. #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct CompilerDoc { +pub struct CompilerWithTools { build_compiler: Compiler, target: TargetSelection, stage: u32, } -impl CompilerDoc { +impl CompilerWithTools { /// Document `stage` compiler for the given `target`. pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self { let build_compiler = prepare_doc_compiler(builder, target, stage); @@ -920,12 +930,12 @@ impl CompilerDoc { } } -impl CommandLineStep for CompilerDoc { - type Output = (); +impl CommandLineStep for CompilerWithTools { + type Output = PathBuf; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.alias("compiler-doc") + run.alias("compiler-with-tools") } fn is_default_step(builder: &Builder<'_>) -> bool { @@ -933,19 +943,17 @@ impl CommandLineStep for CompilerDoc { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(CompilerDoc::for_stage(run.builder, run.builder.top_stage, run.target)); + run.builder.ensure(CompilerWithTools::for_stage( + run.builder, + run.builder.top_stage, + run.target, + )); } - /// Generates compiler documentation. - /// - /// This will generate all documentation for compiler and dependencies. - /// Compiler documentation is distributed separately, so we make sure - /// we do not merge it with the other documentation from std, test and - /// proc_macros. This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { - let CompilerDoc { target, build_compiler, stage } = self; + fn run(self, builder: &Builder<'_>) -> Self::Output { + let CompilerWithTools { target, build_compiler, stage } = self; - // This is the intended out directory for compiler documentation. + // This is the intended out directory for combined compiler documentation. let out = builder.compiler_doc_out(target); let _guard = builder.msg(Kind::Doc, "compiler-doc", Mode::Rustc, build_compiler, target); @@ -1030,6 +1038,7 @@ impl CommandLineStep for CompilerDoc { // Handle `--open`. builder.open_in_browser(out.join("index.html")); + out } fn metadata(&self) -> Option { diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index c12085e093faf..3ab481003458d 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -960,7 +960,7 @@ impl<'a> Builder<'a> { doc::CargoBook, doc::Clippy, doc::ClippyBook, - doc::CompilerDoc, + doc::CompilerWithTools, doc::Miri, doc::EmbeddedBook, doc::EditionGuide, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index f72b4943a177a..a9902c719683e 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2441,6 +2441,31 @@ mod snapshot { "); } + #[test] + fn doc_combined_compiler() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .arg("combined-compiler-doc") + .render_steps(), @" + [build] rustdoc 0 + [build] llvm + [doc] rustc 0 -> rustc 1 + [doc] rustc 0 -> BuildHelper 1 + [build] rustc 0 -> rustc 1 + [doc] rustc 0 -> Rustdoc 1 + [doc] rustc 0 -> Rustfmt 1 + [doc] rustc 0 -> Clippy 1 + [doc] rustc 0 -> Miri 1 + [doc] rustc 0 -> Cargo 1 + [doc] rustc 0 -> Tidy 1 + [doc] rustc 0 -> Bootstrap 1 + [doc] rustc 0 -> RunMakeSupport 1 + [doc] rustc 0 -> Compiletest 1 + [doc] rustc 0 -> compiler-doc 1 + "); + } + #[test] fn doc_cargo_stage_1() { let ctx = TestCtx::new(); diff --git a/src/bootstrap/src/core/session.rs b/src/bootstrap/src/core/session.rs index d1829762b4072..954aee7559013 100644 --- a/src/bootstrap/src/core/session.rs +++ b/src/bootstrap/src/core/session.rs @@ -811,7 +811,7 @@ impl Session { self.out.join(target).join("json-doc") } - /// Output directory for all documentation for a target + /// Output directory for combined compiler + tools docs. pub(crate) fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("compiler-doc") } From 24f1313d01f8e0502ec3b3ba976468172b59ccce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 25 Aug 2026 16:14:18 +0200 Subject: [PATCH 07/11] Explicitly use `CompilerWithTools` for `x dist rustc-docs` --- src/bootstrap/src/core/build_steps/dist.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 871c2ac3b43e9..ef3607bad42f7 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -23,7 +23,7 @@ use crate::core::backend::CodegenBackendKind; use crate::core::build_steps::compile::{ get_codegen_backend_file, libgccjit_path_relative_to_cg_dir, normalize_codegen_backend_name, }; -use crate::core::build_steps::doc::DocumentationFormat; +use crate::core::build_steps::doc::{CompilerWithTools, DocumentationFormat}; use crate::core::build_steps::gcc::GccTargetPair; use crate::core::build_steps::llvm::{ LLVM_CI_LINK_TYPE_PATH, LlvmBuildStatus, get_llvm_build_status, @@ -185,7 +185,7 @@ impl CommandLineStep for JsonDocs { } } -/// Builds the `rustc-docs` installer component. +/// Builds the `rustc-docs` component. /// Apart from the documentation of the `rustc_*` crates, it also includes the documentation of /// various in-tree helper tools (bootstrap, build_helper, tidy), /// and also rustc_private tools like rustdoc, clippy, miri or rustfmt. @@ -214,11 +214,12 @@ impl CommandLineStep for RustcDocs { fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; - builder.run_default_doc_steps(); + let combined_docs = + builder.ensure(CompilerWithTools::for_stage(builder, builder.top_stage, self.target)); let mut tarball = Tarball::new(builder, "rustc-docs", &target.triple); tarball.set_product_name("Rustc Documentation"); - tarball.add_bulk_dir(builder.compiler_doc_out(target), "share/doc/rust/html/rustc-docs"); + tarball.add_bulk_dir(combined_docs, "share/doc/rust/html/rustc-docs"); tarball.generate() } } From 13df8ffd062f4b5d1331e2d7254bcdb09bcf4334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 10:02:57 +0200 Subject: [PATCH 08/11] Return built directory path from the `doc::Rustc` step --- src/bootstrap/src/core/build_steps/doc.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 4df9af858064b..2e3802856f68b 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -959,16 +959,15 @@ impl CommandLineStep for CompilerWithTools { let _guard = builder.msg(Kind::Doc, "compiler-doc", Mode::Rustc, build_compiler, target); let rustc_stage = Rustc::for_stage(builder, stage, target); - builder.ensure(rustc_stage.clone()); - let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target); + let out_dir = builder.ensure(rustc_stage.clone()); // Copy crate docs into place. for krate in &*rustc_stage.crates { let dir_name = krate.replace('-', "_"); - let crate_doc_dir = out_dir.join("doc").join(&dir_name); + let crate_doc_dir = out_dir.join(&dir_name); let doc_out = out.join(&dir_name); t!(fs::create_dir_all(&doc_out)); builder.cp_link_r(&crate_doc_dir, &doc_out); - let crate_src_dir = out_dir.join("doc").join("src").join(&dir_name); + let crate_src_dir = out_dir.join("src").join(&dir_name); let src_out = out.join("src").join(&dir_name); t!(fs::create_dir_all(&src_out)); builder.cp_link_r(&crate_src_dir, &src_out); @@ -1047,6 +1046,8 @@ impl CommandLineStep for CompilerWithTools { } /// Document the compiler for the given `target` using rustdoc from `build_compiler`. +/// +/// Return the path to the generated rustc documentation directory. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustc { build_compiler: Compiler, @@ -1076,7 +1077,7 @@ impl Rustc { } impl CommandLineStep for Rustc { - type Output = (); + type Output = PathBuf; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -1097,7 +1098,7 @@ impl CommandLineStep for Rustc { /// Compiler documentation is distributed separately, so we make sure /// we do not merge it with the other documentation from std, test and /// proc_macros. This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { + fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; // Build the standard library, so that proc-macros can use it. @@ -1205,10 +1206,12 @@ impl CommandLineStep for Rustc { // Let's open the first crate documentation page: out_dir.join(krate).join("index.html") } else { - out_dir + out_dir.clone() }; builder.open_in_browser(index); } + + out_dir } fn metadata(&self) -> Option { From 07c9b8ac0a88770c686bd44608d2e11c744bb5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 26 Aug 2026 10:38:06 +0200 Subject: [PATCH 09/11] Extract documentation artifacts from Cargo --- src/bootstrap/src/core/build_steps/compile.rs | 10 +- src/bootstrap/src/core/build_steps/doc.rs | 417 ++++++++++-------- src/bootstrap/src/core/builder/cargo.rs | 3 + src/bootstrap/src/core/builder/tests.rs | 100 +---- 4 files changed, 271 insertions(+), 259 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 6e2db69d13370..03527d6a07e56 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2677,7 +2677,7 @@ pub fn run_cargo( let (filenames_vec, crate_types) = match msg { CargoMessage::CompilerArtifact { filenames, - target: CargoTarget { crate_types }, + target: CargoTarget { crate_types, .. }, .. } => { let mut f: Vec = filenames.into_iter().map(|s| s.into_owned()).collect(); @@ -2884,12 +2884,14 @@ pub fn stream_cargo( status.success() } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct CargoTarget<'a> { - crate_types: Vec>, + pub crate_types: Vec>, + #[serde(default)] + pub doc: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(tag = "reason", rename_all = "kebab-case")] pub enum CargoMessage<'a> { CompilerArtifact { filenames: Vec>, target: CargoTarget<'a> }, diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 2e3802856f68b..11be686b875e0 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -7,11 +7,13 @@ //! Everything here is basically just a shim around calling either `rustbook` or //! `rustdoc`. +use std::collections::HashSet; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::{env, fs, mem}; use crate::core::build_steps::compile; +use crate::core::build_steps::compile::{CargoMessage, stream_cargo}; use crate::core::build_steps::tool::{ self, RustcPrivateCompilers, SourceType, Tool, prepare_tool_cargo, }; @@ -22,7 +24,7 @@ use crate::core::builder::{ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; use crate::core::session::{FileType, Mode}; -use crate::utils::helpers::{submodule_path_of, symlink_dir, t, up_to_date}; +use crate::utils::helpers::{exit_process, submodule_path_of, symlink_dir, t, up_to_date}; macro_rules! book { ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => { @@ -870,41 +872,32 @@ pub fn prepare_doc_compiler( build_compiler } -fn merge_rustdoc_cci_parts_from_fingerprints( +/// Run rustdoc to merge cross-crate info metadata (like the search index) from individual +/// executions of rustdoc into `out_dir`. +/// The `json_files` parameter should contain paths to JSON file artifacts generated by previous +/// executions of `cargo doc`. +fn merge_rustdoc_cci( builder: &Builder<'_>, build_compiler: Compiler, - fingerprints: impl IntoIterator>, - out_dir: impl AsRef, + json_files: &[PathBuf], + out_dir: &Path, ) { let mut cmd = builder.rustdoc_cmd(build_compiler); - cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(&out_dir.as_ref()); + cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(out_dir); if !builder.config.docs_minification { cmd.arg("--disable-minification"); } - #[derive(serde_derive::Deserialize)] - struct FingerprintData { - doc_parts: Vec, - } - - for fingerprint in fingerprints { - let fingerprint_file = fingerprint.as_ref().join(".rustdoc_fingerprint.json"); - if fingerprint_file.exists() { - let fingerprint_data = t!(std::fs::read_to_string(fingerprint_file)); - let fingerprint_data: FingerprintData = t!(serde_json::from_str(&fingerprint_data)); - for part in fingerprint_data.doc_parts.iter() { - cmd.arg("--read-doc-meta-dir") - .arg(fingerprint.as_ref().join(part).parent().unwrap()); - } - } + for json_file in json_files { + cmd.arg("--read-doc-meta-dir").arg(json_file.parent().unwrap()); } cmd.run(builder); } -/// Generate the combined compiler docs for a given toolchain. +/// Generate the combined compiler + tools docs for a given toolchain. /// This contains both the compiler docs, docs of rustc_private tools (miri, clippy, etc.), cargo /// and also some bootstrap related tools (bootstrap itself, compiletest, tidy, etc.). /// @@ -955,84 +948,59 @@ impl CommandLineStep for CompilerWithTools { // This is the intended out directory for combined compiler documentation. let out = builder.compiler_doc_out(target); - - let _guard = builder.msg(Kind::Doc, "compiler-doc", Mode::Rustc, build_compiler, target); - - let rustc_stage = Rustc::for_stage(builder, stage, target); - let out_dir = builder.ensure(rustc_stage.clone()); - // Copy crate docs into place. - for krate in &*rustc_stage.crates { - let dir_name = krate.replace('-', "_"); - let crate_doc_dir = out_dir.join(&dir_name); - let doc_out = out.join(&dir_name); - t!(fs::create_dir_all(&doc_out)); - builder.cp_link_r(&crate_doc_dir, &doc_out); - let crate_src_dir = out_dir.join("src").join(&dir_name); - let src_out = out.join("src").join(&dir_name); - t!(fs::create_dir_all(&src_out)); - builder.cp_link_r(&crate_src_dir, &src_out); - // Making sure the directory exists and is not empty. - if !builder.config.dry_run() { - assert!(doc_out.exists(), "{}", doc_out.display()); - assert!( - doc_out.read_dir().expect(&dir_name).next().is_some(), - "{}", - doc_out.display() - ); - } - } - - macro_rules! merge_tool_doc { - ($tool: ident, $builder: ident, $target: ident) => {{ - let tool_stage = $tool::new($builder, $target); - builder.ensure(tool_stage.clone()); - let out_dir = builder.stage_out(build_compiler, tool_stage.mode).join(target); - for krate in $tool::crates() { - let dir_name = krate.replace('-', "_"); - let crate_doc_dir = out_dir.join("doc").join(&dir_name); - let doc_out = out.join(&dir_name); - t!(fs::create_dir_all(&doc_out)); - builder.cp_link_r(&crate_doc_dir, &doc_out); - let crate_src_dir = out_dir.join("doc").join("src").join(&dir_name); - let src_out = out.join("src").join(&dir_name); - t!(fs::create_dir_all(&src_out)); - builder.cp_link_r(&crate_src_dir, &src_out); - // Making sure the directory exists and is not empty. - if !builder.config.dry_run() { - assert!(doc_out.exists(), "{}", doc_out.display()); - assert!( - doc_out.read_dir().expect(&dir_name).next().is_some(), - "{}", - doc_out.display() - ); - } - } - }}; - } - - merge_tool_doc!(BuildHelper, builder, target); - merge_tool_doc!(Rustdoc, builder, target); - merge_tool_doc!(Rustfmt, builder, target); - merge_tool_doc!(Clippy, builder, target); - merge_tool_doc!(Miri, builder, target); - merge_tool_doc!(Cargo, builder, target); - merge_tool_doc!(Tidy, builder, target); - merge_tool_doc!(Bootstrap, builder, target); - merge_tool_doc!(RunMakeSupport, builder, target); - merge_tool_doc!(Compiletest, builder, target); + let _ = fs::remove_dir_all(&out); + + let _guard = + builder.msg(Kind::Doc, "compiler-with-tools", Mode::Rustc, build_compiler, target); + + let combined_docs = vec![ + builder.ensure(Rustc::for_stage(builder, stage, target)), + builder.ensure(Rustdoc::new(builder, target)), + builder.ensure(Rustfmt::new(builder, target)), + builder.ensure(Clippy::new(builder, target)), + builder.ensure(Miri::new(builder, target)), + builder.ensure(Cargo::new(builder, target)), + builder.ensure(Tidy::new(builder, target)), + builder.ensure(Bootstrap::new(builder, target)), + builder.ensure(BuildHelper::new(builder, target)), + builder.ensure(Compiletest::new(builder, target)), + builder.ensure(RunMakeSupport::new(builder, target)), + ]; if !builder.config.dry_run() { - merge_rustdoc_cci_parts_from_fingerprints( - builder, - build_compiler, - [ - builder.stage_out(build_compiler, Mode::Rustc).join(target), - builder.stage_out(build_compiler, Mode::ToolTarget).join(target), - builder.stage_out(build_compiler, Mode::Rustc), - builder.stage_out(build_compiler, Mode::ToolTarget), - ], - &out, - ) + // Now copy all the individual docs into a single directory + let mut json_files = vec![]; + for docs in combined_docs { + json_files.extend(docs.artifacts.json_files); + + // Doc directories to link to the shared output directory + // We add the host doc dirs, which should already be symlinked in the target + // docs dir at this point (see `merge_host_and_target_docs`). + let dirs_to_copy: Vec<_> = docs + .artifacts + .target_dirs + .iter() + .chain(docs.artifacts.host_dirs.iter()) + .map(|d| d.file_name().unwrap().to_str().unwrap()) + .collect(); + for dir in dirs_to_copy { + // Link the docs dir + let docs_dir = docs.out_dir.join(dir); + assert!(docs_dir.exists(), "Docs directory {docs_dir:?} does not exist."); + let out_docs_dir = out.join(dir); + builder.create_dir(&out_docs_dir); + builder.cp_link_r(&docs_dir, &out_docs_dir); + + // And the src dir + let src_dir = docs.out_dir.join("src").join(dir); + assert!(src_dir.exists(), "Docs source directory {src_dir:?} does not exist."); + let out_src_dir = out.join("src").join(dir); + builder.create_dir(&out_src_dir); + builder.cp_link_r(&src_dir, &out_src_dir); + } + } + // And finally merge all the CCI metadata + merge_rustdoc_cci(builder, build_compiler, &json_files, &out); } // Handle `--open`. @@ -1041,10 +1009,19 @@ impl CommandLineStep for CompilerWithTools { } fn metadata(&self) -> Option { - Some(StepMetadata::doc("compiler-doc", self.target).built_by(self.build_compiler)) + Some(StepMetadata::doc("CompilerWithTools", self.target).built_by(self.build_compiler)) } } +/// Output of a Doc step. +#[derive(Clone)] +pub struct BuiltDocs { + /// Target doc directory with the generated documentation. + out_dir: PathBuf, + /// Doc artifacts gathered from Cargo during the doc build. + artifacts: DocArtifacts, +} + /// Document the compiler for the given `target` using rustdoc from `build_compiler`. /// /// Return the path to the generated rustc documentation directory. @@ -1077,7 +1054,7 @@ impl Rustc { } impl CommandLineStep for Rustc { - type Output = PathBuf; + type Output = BuiltDocs; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -1148,52 +1125,24 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("--extern-html-root-url"); cargo.rustdocflag("ena=https://docs.rs/ena/latest/"); - let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc"); - let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc"); + let cargo_target_dir = builder.stage_out(build_compiler, Mode::Rustc); + let target_doc_dir = cargo_target_dir.join(target).join("doc"); + let host_doc_dir = cargo_target_dir.join("doc"); for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses // relative links. // FIXME: Cargo should probably do this itself. - let dir_name = krate.replace('-', "_"); - t!(fs::create_dir_all(out_dir.join(&*dir_name))); + let dir_name = normalize_doc_crate_name(krate); + t!(fs::create_dir_all(target_doc_dir.join(&*dir_name))); cargo.arg("-p").arg(krate); } - cargo.into_cmd().run(builder); + let artifacts = create_docs_and_gather_artifacts(builder, cargo); + artifacts.sanity_check_crates(builder, self.crates.iter()); if !builder.config.dry_run() { - // Sanity check on linked doc directories - for krate in &*self.crates { - let dir_name = krate.replace("-", "_"); - // Making sure the directory exists and is not empty. - let doc_out = out_dir.join(&*dir_name); - let proc_macro_doc_out = proc_macro_out_dir.join(&*dir_name); - if proc_macro_doc_out.exists() - && doc_out.read_dir().expect(&dir_name).next().is_none() - { - // Cargo puts proc macros in `target/doc` even if you pass `--target` - // explicitly (https://github.com/rust-lang/cargo/issues/7677). - t!(fs::remove_dir(&doc_out)); - t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); - let src_out = out_dir.join("src").join(&*dir_name); - let proc_macro_src_out = proc_macro_out_dir.join("src").join(&*dir_name); - t!(symlink_dir(&builder.config, &proc_macro_src_out, &src_out)); - } - assert!(doc_out.exists(), "{}", doc_out.display()); - assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); - } - } - - if !builder.config.dry_run() { - merge_rustdoc_cci_parts_from_fingerprints( - builder, - build_compiler, - [ - builder.stage_out(build_compiler, Mode::Rustc).join(target), - builder.stage_out(build_compiler, Mode::Rustc), - ], - &out_dir, - ) + merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir); + merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir); } // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md` @@ -1201,17 +1150,17 @@ impl CommandLineStep for Rustc { if builder.was_invoked_explicitly::(Kind::Doc) { let index = if builder.paths.iter().any(|path| path.ends_with("compiler")) { // For `x.py doc compiler --open`, open `rustc_middle` by default. - out_dir.join("rustc_middle").join("index.html") + target_doc_dir.join("rustc_middle").join("index.html") } else if let Some(krate) = self.crates.first() { // Let's open the first crate documentation page: - out_dir.join(krate).join("index.html") + target_doc_dir.join(krate).join("index.html") } else { - out_dir.clone() + target_doc_dir.clone() }; builder.open_in_browser(index); } - out_dir + BuiltDocs { out_dir: target_doc_dir, artifacts } } fn metadata(&self) -> Option { @@ -1219,6 +1168,145 @@ impl CommandLineStep for Rustc { } } +/// Stores generated documentation artifacts. +#[derive(Clone, Debug)] +struct DocArtifacts { + /// Directories with HTML docs for host (proc-macro) crates. + host_dirs: Vec, + /// Directories with HTML docs for target crates. + target_dirs: Vec, + /// JSON files used to create the final CCI index + json_files: Vec, +} + +impl DocArtifacts { + /// Ensure that all passed crates were documented. + fn sanity_check_crates(&self, builder: &Builder<'_>, crates: impl Iterator) + where + S: AsRef, + { + if builder.config.dry_run() { + return; + } + let crate_names: HashSet<&str> = self + .host_dirs + .iter() + .chain(self.target_dirs.iter()) + .filter_map(|d| d.file_name().and_then(|d| d.to_str())) + .collect(); + for krate in crates { + let krate = krate.as_ref(); + let krate = normalize_doc_crate_name(krate); + if !crate_names.contains(krate.as_str()) { + eprintln!("ERROR: crate {krate} was not documented!"); + exit_process(1); + } + } + } +} + +/// Run `cargo doc` and gather generated documentation artifacts. +fn create_docs_and_gather_artifacts(builder: &Builder<'_>, cargo: builder::Cargo) -> DocArtifacts { + let mut json_files = vec![]; + let mut host_dirs = vec![]; + let mut target_dirs = vec![]; + stream_cargo(builder, cargo, vec![], &mut |msg| { + let CargoMessage::CompilerArtifact { filenames, target } = msg else { + return; + }; + if !target.doc { + return; + } + // Note: An alternative way to check host docs would be to check whether the generated + // output is a child of the host doc directory (which we would have to pass to this + // function). + let is_host = target.crate_types.iter().any(|t| t == "proc-macro"); + for filename in filenames { + let path = Path::new(filename.as_ref()); + let Some(extension) = path.extension().and_then(|ext| ext.to_str()) else { + continue; + }; + let path = path.to_path_buf(); + if extension == "json" { + json_files.push(path.to_path_buf()); + } else if extension == "html" { + if is_host { + // doc//index.html -> doc/ + host_dirs.push(path.parent().unwrap().to_path_buf()); + } else { + target_dirs.push(path.parent().unwrap().to_path_buf()); + } + } + } + }); + DocArtifacts { host_dirs, target_dirs, json_files } +} + +/// Merge host and target documentation for a set of crates. +/// We pass `--target` when documenting, so Cargo will put the built documentation into two places: +/// - `target/doc` - contains documentation of host code, so proc macros +/// - `target//doc` - contains documentation of "normal" code +/// +/// See https://github.com/rust-lang/cargo/issues/7677. +/// +/// To produce a single unified documentation, we want to merge them together. +/// We do that creating symlinks into the target doc dir that will point to the host doc +/// directories. +/// The target doc directory will then contain the combined docs. +fn merge_host_and_target_docs( + builder: &Builder<'_>, + docs: &DocArtifacts, + host_doc_dir: &Path, + target_doc_dir: &Path, +) { + // Sanity check that there is no host/target overlap + for dir in &docs.host_dirs { + let name = dir.file_name().and_then(|d| d.to_str()).unwrap(); + if let Some(target_dir) = docs.target_dirs.iter().find_map(|d| { + let dirname = d.file_name().and_then(|d| d.to_str())?; + if dirname == name { Some(d) } else { None } + }) { + eprintln!( + "ERROR: host docs directory `{name}` ({dir:?}) is also contained in target doc directory ({target_dir:?})" + ); + exit_process(1); + } + } + + let target_src_dir = target_doc_dir.join("src"); + let host_src_dir = host_doc_dir.join("src"); + + // Ideally, we would remove all previous symlinks here. + // However, some of the tools actually share the same build docs directory, so we shouldn't do + // that, otherwise they will invalidate one another. + + for host_docs_crate in &docs.host_dirs { + let dir_name = host_docs_crate.file_name().unwrap().to_str().unwrap(); + // Normalize crate name + let dir_name = normalize_doc_crate_name(dir_name); + + t!(symlink_dir(&builder.config, host_docs_crate, &target_doc_dir.join(&dir_name))); + + // Also symlink its source directory + let target_src_out = target_src_dir.join(&dir_name); + let host_src_out = host_src_dir.join(&dir_name); + t!(symlink_dir(&builder.config, &host_src_out, &target_src_out)); + } + + // Sanity check that all directories contain some documentation + for dir in docs.target_dirs.iter().chain(docs.host_dirs.iter()) { + // Making sure the directory exists and is not empty. + assert!(dir.exists(), "Doc directory {dir:?} does not exist"); + assert!(t!(dir.read_dir()).next().is_some(), "Doc directory {dir:?} is empty"); + } +} + +/// Normalizes crate name to get a name that is used to generate documentation on disk. +/// Turns `rustc-main` into `rustc_main`. +fn normalize_doc_crate_name(name: &str) -> String { + name.replace("-", "_") +} + macro_rules! tool_doc { ( $tool: ident, @@ -1264,7 +1352,7 @@ macro_rules! tool_doc { } impl CommandLineStep for $tool { - type Output = (); + type Output = BuiltDocs; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -1282,7 +1370,7 @@ macro_rules! tool_doc { /// Generates documentation for a tool. /// /// This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { + fn run(self, builder: &Builder<'_>) -> Self::Output { let mut source_type = SourceType::InTree; if let Some(submodule_path) = submodule_path_of(&builder, $path) { @@ -1292,10 +1380,6 @@ macro_rules! tool_doc { let $tool { build_compiler, mode, target } = self; - // This is the intended out directory for compiler documentation. - let out = builder.compiler_doc_out(target); - t!(fs::create_dir_all(&out)); - // Build cargo command. let mut cargo = prepare_tool_cargo( builder, @@ -1324,9 +1408,9 @@ macro_rules! tool_doc { cargo.arg("--lib"); } - $(for krate in $crates { + for krate in $tool::crates() { cargo.arg("-p").arg(krate); - })? + } cargo.rustdocflag("--document-private-items"); // Since we always pass --document-private-items, there's no need to warn about linking to private items. @@ -1335,36 +1419,23 @@ macro_rules! tool_doc { cargo.rustdocflag("--show-type-layout"); cargo.rustdocflag("--generate-link-to-definition"); - let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc"); - let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc"); - $(for krate in $crates { - let dir_name = krate.replace("-", "_"); - t!(fs::create_dir_all(out_dir.join(&*dir_name))); - })? + let cargo_target_dir = builder.stage_out(build_compiler, mode); + let target_doc_dir = cargo_target_dir.join(target).join("doc"); + let host_doc_dir = cargo_target_dir.join("doc"); + for krate in $tool::crates() { + let dir_name = normalize_doc_crate_name(krate); + t!(fs::create_dir_all(target_doc_dir.join(&*dir_name))); + } let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target); - cargo.into_cmd().run(builder); + let artifacts = create_docs_and_gather_artifacts(builder, cargo); + artifacts.sanity_check_crates(builder, $tool::crates().iter()); if !builder.config.dry_run() { - // Sanity check on linked doc directories - $(for krate in $crates { - let dir_name = krate.replace("-", "_"); - // Making sure the directory exists and is not empty. - let doc_out = out_dir.join(&*dir_name); - let proc_macro_doc_out = proc_macro_out_dir.join(&*dir_name); - if proc_macro_doc_out.exists() && doc_out.read_dir().expect(&dir_name).next().is_none() { - // Cargo puts proc macros in `target/doc` even if you pass `--target` - // explicitly (https://github.com/rust-lang/cargo/issues/7677). - t!(fs::remove_dir(&doc_out)); - t!(symlink_dir(&builder.config, &proc_macro_doc_out, &doc_out)); - let src_out = out_dir.join("src").join(&*dir_name); - let proc_macro_src_out = proc_macro_out_dir.join("src").join(&*dir_name); - t!(symlink_dir(&builder.config, &proc_macro_src_out, &src_out)); - } - assert!(doc_out.exists(), "{}", doc_out.display()); - assert!(doc_out.read_dir().expect(&dir_name).next().is_some()); - })? + merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir); + merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir); } + BuiltDocs { out_dir: target_doc_dir, artifacts } } fn metadata(&self) -> Option { diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 4d4984fd9b23a..0b788d66c37dd 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -727,6 +727,9 @@ impl Builder<'_> { // Will be stabilized soon -> let's dogfood it. // No effect on doc output but massive doc-generation time improvements. cargo.arg("-Zrustdoc-mergeable-info"); + + // FIXME: remove this directory clearing here, and do it explicitly in individua doc + // steps, to reduce dependency on implicit doc output paths. let my_out = match mode { // This is the intended out directory for compiler documentation. Mode::Rustc | Mode::ToolRustcPrivate | Mode::ToolBootstrap | Mode::ToolTarget => { diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index a9902c719683e..8ecc057baf4bb 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -989,59 +989,6 @@ mod snapshot { ); } - #[test] - fn dist_compiler_docs() { - let ctx = TestCtx::new(); - insta::assert_snapshot!( - ctx.config("dist") - .path("rustc-docs") - .args(&["--set", "build.compiler-docs=true"]) - .render_steps(), @r" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> UnstableBookGen 1 - [build] rustc 0 -> Rustbook 1 - [doc] unstable-book (book) - [doc] book (book) - [doc] book/first-edition (book) - [doc] book/second-edition (book) - [doc] book/2018-edition (book) - [build] rustdoc 1 - [doc] rustc 1 -> standalone 2 - [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [doc] rustc 1 -> rustc 2 - [build] rustc 1 -> rustc 2 - [doc] rustc 1 -> Rustdoc 2 - [doc] rustc 1 -> Rustfmt 2 - [build] rustc 1 -> error-index 2 - [doc] rustc 1 -> error-index 2 - [doc] nomicon (book) - [doc] rustc 1 -> reference (book) 2 - [doc] rustdoc (book) - [doc] rust-by-example (book) - [build] rustc 0 -> LintDocs 1 - [doc] rustc (book) - [doc] rustc 1 -> Cargo 2 - [doc] cargo (book) - [doc] rustc 1 -> Clippy 2 - [doc] clippy (book) - [doc] rustc 1 -> BuildHelper 2 - [doc] rustc 1 -> Miri 2 - [doc] rustc 1 -> Tidy 2 - [doc] rustc 1 -> Bootstrap 2 - [doc] rustc 1 -> RunMakeSupport 2 - [doc] rustc 1 -> Compiletest 2 - [doc] rustc 1 -> compiler-doc 2 - [doc] embedded-book (book) - [doc] edition-guide (book) - [doc] style-guide (book) - [doc] rustc 1 -> releases 2 - [build] rustc 0 -> RustInstaller 1 - " - ); - } - #[test] fn dist_extended() { let ctx = TestCtx::new(); @@ -1625,35 +1572,24 @@ mod snapshot { ctx .config("dist") .path("rustc-docs") - .render_steps(), @r" + .render_steps(), @" [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 0 -> UnstableBookGen 1 - [build] rustc 0 -> Rustbook 1 - [doc] unstable-book (book) - [doc] book (book) - [doc] book/first-edition (book) - [doc] book/second-edition (book) - [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] rustc 1 -> standalone 2 - [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [doc] rustc 1 -> rustc 2 [build] rustc 1 -> rustc 2 - [build] rustc 1 -> error-index 2 - [doc] rustc 1 -> error-index 2 - [doc] nomicon (book) - [doc] rustc 1 -> reference (book) 2 - [doc] rustdoc (book) - [doc] rust-by-example (book) - [build] rustc 0 -> LintDocs 1 - [doc] rustc (book) - [doc] cargo (book) - [doc] clippy (book) - [doc] embedded-book (book) - [doc] edition-guide (book) - [doc] style-guide (book) - [doc] rustc 1 -> releases 2 + [doc] rustc 1 -> Rustdoc 2 + [doc] rustc 1 -> Rustfmt 2 + [doc] rustc 1 -> Clippy 2 + [doc] rustc 1 -> Miri 2 + [doc] rustc 1 -> Cargo 2 + [doc] rustc 1 -> Tidy 2 + [doc] rustc 1 -> Bootstrap 2 + [doc] rustc 1 -> BuildHelper 2 + [doc] rustc 1 -> Compiletest 2 + [doc] rustc 1 -> RunMakeSupport 2 + [doc] rustc 1 -> CompilerWithTools 2 [build] rustc 0 -> RustInstaller 1 "); } @@ -2442,16 +2378,15 @@ mod snapshot { } #[test] - fn doc_combined_compiler() { + fn doc_compiler_with_tools() { let ctx = TestCtx::new(); insta::assert_snapshot!( ctx.config("doc") - .arg("combined-compiler-doc") + .arg("compiler-with-tools") .render_steps(), @" [build] rustdoc 0 [build] llvm [doc] rustc 0 -> rustc 1 - [doc] rustc 0 -> BuildHelper 1 [build] rustc 0 -> rustc 1 [doc] rustc 0 -> Rustdoc 1 [doc] rustc 0 -> Rustfmt 1 @@ -2460,9 +2395,10 @@ mod snapshot { [doc] rustc 0 -> Cargo 1 [doc] rustc 0 -> Tidy 1 [doc] rustc 0 -> Bootstrap 1 - [doc] rustc 0 -> RunMakeSupport 1 + [doc] rustc 0 -> BuildHelper 1 [doc] rustc 0 -> Compiletest 1 - [doc] rustc 0 -> compiler-doc 1 + [doc] rustc 0 -> RunMakeSupport 1 + [doc] rustc 0 -> CompilerWithTools 1 "); } From 389f8124d1160f47cee1909bf414ce682062941d Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 1 Sep 2026 19:06:47 +0800 Subject: [PATCH 10/11] Fix comment --- src/bootstrap/src/core/build_steps/doc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 11be686b875e0..4ae56503a114e 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1250,7 +1250,7 @@ fn create_docs_and_gather_artifacts(builder: &Builder<'_>, cargo: builder::Cargo /// See https://github.com/rust-lang/cargo/issues/7677. /// /// To produce a single unified documentation, we want to merge them together. -/// We do that creating symlinks into the target doc dir that will point to the host doc +/// We do that by creating symlinks into the target doc dir that will point to the host doc /// directories. /// The target doc directory will then contain the combined docs. fn merge_host_and_target_docs( From 5b2dd1af14ebc3577265ba725a7302f037d0b515 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 1 Sep 2026 19:27:24 +0800 Subject: [PATCH 11/11] Rebless bootstrap snapshot test Just step ordering. --- src/bootstrap/src/core/builder/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 8ecc057baf4bb..72004ef65ede3 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2385,8 +2385,8 @@ mod snapshot { .arg("compiler-with-tools") .render_steps(), @" [build] rustdoc 0 - [build] llvm [doc] rustc 0 -> rustc 1 + [build] llvm [build] rustc 0 -> rustc 1 [doc] rustc 0 -> Rustdoc 1 [doc] rustc 0 -> Rustfmt 1