From 4795170f80c895364f772ffb71cbe753758c680d Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 5 Sep 2026 22:52:07 +1000 Subject: [PATCH 1/3] Consistently use struct syntax in `tool_doc!` A major advantage of struct syntax in macros is that rustfmt will auto-format the macro invocations. --- src/bootstrap/src/core/build_steps/doc.rs | 120 ++++++++++------------ 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 4ae56503a114e..b43737c985a10 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1309,14 +1309,16 @@ fn normalize_doc_crate_name(name: &str) -> String { macro_rules! tool_doc { ( - $tool: ident, - $path: literal, - mode = $mode:expr - $(, is_library = $is_library:expr )? - $(, crates = $crates:expr )? - // Subset of nightly features that are allowed to be used when documenting - $(, allow_features: $allow_features:expr )? - ) => { + $tool:ident { + path: $path:literal + , mode: $mode:expr + $(, is_library: $is_library:expr )? + $(, crates: $crates:expr )? + // Subset of nightly features that are allowed to be used when documenting + $(, allow_features: $allow_features:expr )? + $(,)? + } + ) => { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct $tool { build_compiler: Compiler, @@ -1446,41 +1448,36 @@ macro_rules! tool_doc { } // NOTE: make sure to register these in `Builder::get_step_description`. -tool_doc!( - BuildHelper, - "src/build_helper", +tool_doc!(BuildHelper { + path: "src/build_helper", // ideally, this would use ToolBootstrap, // but we distribute these docs together in the same folder // as a bunch of stage1 tools, and you can't mix rustdoc versions // because that breaks cross-crate data (particularly search) - mode = Mode::ToolTarget, - is_library = true, - crates = ["build_helper"] -); -tool_doc!( - Rustdoc, - "src/tools/rustdoc", - mode = Mode::ToolRustcPrivate, - crates = ["rustdoc", "rustdoc-json-types"] -); -tool_doc!( - Rustfmt, - "src/tools/rustfmt", - mode = Mode::ToolRustcPrivate, - crates = ["rustfmt-nightly", "rustfmt-config_proc_macro"] -); -tool_doc!( - Clippy, - "src/tools/clippy", - mode = Mode::ToolRustcPrivate, - crates = ["clippy_config", "clippy_utils"] -); -tool_doc!(Miri, "src/tools/miri", mode = Mode::ToolRustcPrivate, crates = ["miri"]); -tool_doc!( - Cargo, - "src/tools/cargo", - mode = Mode::ToolTarget, - crates = [ + mode: Mode::ToolTarget, + is_library: true, + crates: ["build_helper"], +}); +tool_doc!(Rustdoc { + path: "src/tools/rustdoc", + mode: Mode::ToolRustcPrivate, + crates: ["rustdoc", "rustdoc-json-types"], +}); +tool_doc!(Rustfmt { + path: "src/tools/rustfmt", + mode: Mode::ToolRustcPrivate, + crates: ["rustfmt-nightly", "rustfmt-config_proc_macro"], +}); +tool_doc!(Clippy { + path: "src/tools/clippy", + mode: Mode::ToolRustcPrivate, + crates: ["clippy_config", "clippy_utils"], +}); +tool_doc!(Miri { path: "src/tools/miri", mode: Mode::ToolRustcPrivate, crates: ["miri"] }); +tool_doc!(Cargo { + path: "src/tools/cargo", + mode: Mode::ToolTarget, + crates: [ "cargo", "cargo-credential", "cargo-platform", @@ -1494,30 +1491,27 @@ tool_doc!( ], // Required because of the im-rc dependency of Cargo, which automatically opts into the // "specialization" feature in its build script when it detects a nightly toolchain. - allow_features: "specialization" -); -tool_doc!(Tidy, "src/tools/tidy", mode = Mode::ToolTarget, crates = ["tidy"]); -tool_doc!( - Bootstrap, - "src/bootstrap", - mode = Mode::ToolTarget, - is_library = true, - crates = ["bootstrap"] -); -tool_doc!( - RunMakeSupport, - "src/tools/run-make-support", - mode = Mode::ToolTarget, - is_library = true, - crates = ["run_make_support"] -); -tool_doc!( - Compiletest, - "src/tools/compiletest", - mode = Mode::ToolTarget, - is_library = true, - crates = ["compiletest"] -); + allow_features: "specialization", +}); +tool_doc!(Tidy { path: "src/tools/tidy", mode: Mode::ToolTarget, crates: ["tidy"] }); +tool_doc!(Bootstrap { + path: "src/bootstrap", + mode: Mode::ToolTarget, + is_library: true, + crates: ["bootstrap"], +}); +tool_doc!(RunMakeSupport { + path: "src/tools/run-make-support", + mode: Mode::ToolTarget, + is_library: true, + crates: ["run_make_support"], +}); +tool_doc!(Compiletest { + path: "src/tools/compiletest", + mode: Mode::ToolTarget, + is_library: true, + crates: ["compiletest"], +}); #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct ErrorIndex { From 0890e738aa8a60c3ac7317c76a127c83441a6ec1 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 4 Sep 2026 16:58:05 +1000 Subject: [PATCH 2/3] Extract most of `tool_doc!` into non-macro code --- src/bootstrap/src/core/build_steps/doc.rs | 204 ++++++++++++---------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index b43737c985a10..f85c85ed4ba6f 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1181,10 +1181,11 @@ struct DocArtifacts { impl DocArtifacts { /// Ensure that all passed crates were documented. - fn sanity_check_crates(&self, builder: &Builder<'_>, crates: impl Iterator) - where - S: AsRef, - { + fn sanity_check_crates( + &self, + builder: &Builder<'_>, + crates: impl IntoIterator>, + ) { if builder.config.dry_run() { return; } @@ -1313,7 +1314,7 @@ macro_rules! tool_doc { path: $path:literal , mode: $mode:expr $(, is_library: $is_library:expr )? - $(, crates: $crates:expr )? + , crates: $crates:expr // Subset of nightly features that are allowed to be used when documenting $(, allow_features: $allow_features:expr )? $(,)? @@ -1322,34 +1323,19 @@ macro_rules! tool_doc { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct $tool { build_compiler: Compiler, - mode: Mode, target: TargetSelection, } impl $tool { + const PATH: &str = $path; + const MODE: Mode = $mode; + const IS_LIBRARY: bool = false $( || $is_library )?; + const CRATES: &[&str] = &$crates; + const ALLOW_FEATURES: Option<&str> = [$( $allow_features )?].first().copied(); + fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool { - let build_compiler = match $mode { - Mode::ToolRustcPrivate => { - // Rustdoc needs the rustc sysroot available to build. - let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target); - - // Build rustc docs so that we generate relative links. - 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(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)?[..] + let build_compiler = compiler_for_tool_doc(builder, $tool::MODE, target); + $tool { build_compiler, target } } } @@ -1358,7 +1344,7 @@ macro_rules! tool_doc { const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path($path) + run.path($tool::PATH) } fn is_default_step(builder: &Builder<'_>) -> bool { @@ -1373,78 +1359,116 @@ macro_rules! tool_doc { /// /// This is largely just a wrapper around `cargo doc`. fn run(self, builder: &Builder<'_>) -> Self::Output { - let mut source_type = SourceType::InTree; - - if let Some(submodule_path) = submodule_path_of(&builder, $path) { - source_type = SourceType::Submodule; - builder.require_submodule(&submodule_path, None); - } - - let $tool { build_compiler, mode, target } = self; - - // Build cargo command. - let mut cargo = prepare_tool_cargo( + let $tool { build_compiler, target } = self; + document_tool( builder, build_compiler, - mode, + $tool::MODE, target, - Kind::Doc, - $path, - source_type, - &[], - ); - let allow_features = { - let mut _value = ""; - $( _value = $allow_features; )? - _value - }; - - if !allow_features.is_empty() { - cargo.allow_features(allow_features); - } + $tool::PATH, + $tool::IS_LIBRARY, + $tool::CRATES, + $tool::ALLOW_FEATURES, + ) + } - // Only include compiler crates, no dependencies of those, such as `libc`. - cargo.arg("--no-deps"); + fn metadata(&self) -> Option { + Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler)) + } + } + } +} - if false $(|| $is_library)? { - cargo.arg("--lib"); - } +fn compiler_for_tool_doc(builder: &Builder<'_>, mode: Mode, target: TargetSelection) -> Compiler { + match mode { + Mode::ToolRustcPrivate => { + // Rustdoc needs the rustc sysroot available to build. + let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target); - for krate in $tool::crates() { - cargo.arg("-p").arg(krate); - } + // Build rustc docs so that we generate relative links. + 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(builder, builder.host_target, builder.top_stage) + } + _ => panic!("Unexpected tool mode for documenting: {mode:?}"), + } +} - cargo.rustdocflag("--document-private-items"); - // Since we always pass --document-private-items, there's no need to warn about linking to private items. - cargo.rustdocflag("-Arustdoc::private-intra-doc-links"); - cargo.rustdocflag("--enable-index-page"); - cargo.rustdocflag("--show-type-layout"); - cargo.rustdocflag("--generate-link-to-definition"); - - 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))); - } +/// Inner implementation of [`CommandLineStep::run`] for the [`tool_doc`] macro. +#[expect(clippy::too_many_arguments)] +fn document_tool( + builder: &Builder<'_>, + build_compiler: Compiler, + mode: Mode, + target: TargetSelection, + path: &str, + is_library: bool, + crates: &[&str], + allow_features: Option<&str>, +) -> BuiltDocs { + let mut source_type = SourceType::InTree; - let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target); - let artifacts = create_docs_and_gather_artifacts(builder, cargo); - artifacts.sanity_check_crates(builder, $tool::crates().iter()); + if let Some(submodule_path) = submodule_path_of(builder, path) { + source_type = SourceType::Submodule; + builder.require_submodule(&submodule_path, None); + } - if !builder.config.dry_run() { - 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 } - } + // Build cargo command. + let mut cargo = prepare_tool_cargo( + builder, + build_compiler, + mode, + target, + Kind::Doc, + path, + source_type, + &[], + ); - fn metadata(&self) -> Option { - Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler)) - } - } + if let Some(allow_features) = allow_features { + cargo.allow_features(allow_features); + } + + // Only include compiler crates, no dependencies of those, such as `libc`. + cargo.arg("--no-deps"); + + if is_library { + cargo.arg("--lib"); + } + + for krate in 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. + cargo.rustdocflag("-Arustdoc::private-intra-doc-links"); + cargo.rustdocflag("--enable-index-page"); + cargo.rustdocflag("--show-type-layout"); + cargo.rustdocflag("--generate-link-to-definition"); + + 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 crates { + let dir_name = normalize_doc_crate_name(krate); + t!(fs::create_dir_all(target_doc_dir.join(&*dir_name))); + } + + let tool_name = Path::new(path).file_name().unwrap().display(); + let _guard = builder.msg(Kind::Doc, tool_name, None, build_compiler, target); + let artifacts = create_docs_and_gather_artifacts(builder, cargo); + artifacts.sanity_check_crates(builder, crates); + + if !builder.config.dry_run() { + 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 } } // NOTE: make sure to register these in `Builder::get_step_description`. From 5fea25d3e93113c2b0fdcda5a0c96eee97d6c63a Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 28 Aug 2026 13:25:35 +1000 Subject: [PATCH 3/3] Include feature-gated items in bootstrap tool docs This sets `--all-features` when documenting bootstrap tool crates, and enables rustdoc's `#![feature(doc_cfg)]` to display which items are feature-gated. --- src/bootstrap/src/core/build_steps/doc.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index f85c85ed4ba6f..87c998f6e0c33 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1317,6 +1317,8 @@ macro_rules! tool_doc { , crates: $crates:expr // Subset of nightly features that are allowed to be used when documenting $(, allow_features: $allow_features:expr )? + // Pass `--all-features` to document all non-default features. + $(, all_features: $all_features:literal )? $(,)? } ) => { @@ -1332,6 +1334,7 @@ macro_rules! tool_doc { const IS_LIBRARY: bool = false $( || $is_library )?; const CRATES: &[&str] = &$crates; const ALLOW_FEATURES: Option<&str> = [$( $allow_features )?].first().copied(); + const ALL_FEATURES: bool = true $( && $all_features )?; fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool { let build_compiler = compiler_for_tool_doc(builder, $tool::MODE, target); @@ -1369,6 +1372,7 @@ macro_rules! tool_doc { $tool::IS_LIBRARY, $tool::CRATES, $tool::ALLOW_FEATURES, + $tool::ALL_FEATURES, ) } @@ -1409,6 +1413,7 @@ fn document_tool( is_library: bool, crates: &[&str], allow_features: Option<&str>, + all_features: bool, ) -> BuiltDocs { let mut source_type = SourceType::InTree; @@ -1444,6 +1449,15 @@ fn document_tool( cargo.arg("-p").arg(krate); } + // Tell rustdoc to document which items require feature flags. + if all_features { + cargo.arg("--all-features"); + } + if allow_features.is_some() { + cargo.allow_features("doc_cfg"); + } + cargo.rustdocflag("-Zcrate-attr=feature(doc_cfg)"); + cargo.rustdocflag("--document-private-items"); // Since we always pass --document-private-items, there's no need to warn about linking to private items. cargo.rustdocflag("-Arustdoc::private-intra-doc-links"); @@ -1491,13 +1505,19 @@ tool_doc!(Rustfmt { path: "src/tools/rustfmt", mode: Mode::ToolRustcPrivate, crates: ["rustfmt-nightly", "rustfmt-config_proc_macro"], + all_features: false, }); tool_doc!(Clippy { path: "src/tools/clippy", mode: Mode::ToolRustcPrivate, crates: ["clippy_config", "clippy_utils"], }); -tool_doc!(Miri { path: "src/tools/miri", mode: Mode::ToolRustcPrivate, crates: ["miri"] }); +tool_doc!(Miri { + path: "src/tools/miri", + mode: Mode::ToolRustcPrivate, + crates: ["miri"], + all_features: false, +}); tool_doc!(Cargo { path: "src/tools/cargo", mode: Mode::ToolTarget,