Skip to main content

bootstrap/core/build_steps/
doc.rs

1//! Documentation generation for bootstrap.
2//!
3//! This module implements generation for all bits and pieces of documentation
4//! for the Rust project. This notably includes suites like the rust book, the
5//! nomicon, rust by example, standalone documentation, etc.
6//!
7//! Everything here is basically just a shim around calling either `rustbook` or
8//! `rustdoc`.
9
10use std::collections::HashSet;
11use std::io::{self, Write};
12use std::path::{Path, PathBuf};
13use std::{env, fs, mem};
14
15use crate::core::build_steps::compile;
16use crate::core::build_steps::compile::{CargoMessage, stream_cargo};
17use crate::core::build_steps::tool::{
18    self, RustcPrivateCompilers, SourceType, Tool, prepare_tool_cargo,
19};
20use crate::core::builder::{
21    self, Builder, CommandLineStep, Kind, RunConfig, ShouldRun, Step, StepMetadata,
22    crate_description,
23};
24use crate::core::compiler::Compiler;
25use crate::core::config::TargetSelection;
26use crate::core::session::{FileType, Mode};
27use crate::utils::helpers::{exit_process, submodule_path_of, symlink_dir, t, up_to_date};
28
29macro_rules! book {
30    ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => {
31        $(
32        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
33        pub struct $name {
34            target: TargetSelection,
35        }
36
37        impl CommandLineStep for $name {
38            type Output = ();
39
40            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
41                run.path($path)
42            }
43
44            fn is_default_step(builder: &Builder<'_>) -> bool {
45                builder.config.docs
46            }
47
48            fn make_run(run: RunConfig<'_>) {
49                run.builder.ensure($name {
50                    target: run.target,
51                });
52            }
53
54            fn run(self, builder: &Builder<'_>) {
55                if let Some(submodule_path) = submodule_path_of(&builder, $path) {
56                    builder.require_submodule(&submodule_path, None)
57                }
58
59                builder.ensure(RustbookSrc {
60                    target: self.target,
61                    name: $book_name.to_owned(),
62                    src: builder.src.join($path),
63                    parent: Some(self),
64                    languages: $lang.into(),
65                    build_compiler: None,
66                })
67            }
68        }
69        )+
70    }
71}
72
73// NOTE: When adding a book here, make sure to ALSO build the book by
74// adding a build step in `src/bootstrap/code/builder/mod.rs`!
75// NOTE: Make sure to add the corresponding submodule when adding a new book.
76book!(
77    CargoBook, "src/tools/cargo/doc/book", "cargo", &[];
78    ClippyBook, "src/tools/clippy/book", "clippy", &[];
79    EditionGuide, "src/doc/edition-guide", "edition-guide", &[];
80    EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[];
81    Nomicon, "src/doc/nomicon", "nomicon", &[];
82    RustByExample, "src/doc/rust-by-example", "rust-by-example", &["es", "ja", "zh", "ko"];
83    RustdocBook, "src/doc/rustdoc", "rustdoc", &[];
84    StyleGuide, "src/doc/style-guide", "style-guide", &[];
85);
86
87#[derive(Debug, Clone, Hash, PartialEq, Eq)]
88pub struct UnstableBook {
89    build_compiler: Compiler,
90    target: TargetSelection,
91}
92
93impl CommandLineStep for UnstableBook {
94    type Output = ();
95
96    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
97        run.path("src/doc/unstable-book")
98    }
99
100    fn is_default_step(builder: &Builder<'_>) -> bool {
101        builder.config.docs
102    }
103
104    fn make_run(run: RunConfig<'_>) {
105        // Bump the stage to 2, because the unstable book requires an in-tree compiler.
106        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
107        // in stage 1.
108        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
109            run.builder.top_stage
110        } else {
111            2
112        };
113
114        run.builder.ensure(UnstableBook {
115            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
116            target: run.target,
117        });
118    }
119
120    fn run(self, builder: &Builder<'_>) {
121        let unstable_book_md_dir = builder
122            .ensure(UnstableBookGen { build_compiler: self.build_compiler, target: self.target });
123        builder.ensure(RustbookSrc {
124            target: self.target,
125            name: "unstable-book".to_owned(),
126            src: unstable_book_md_dir,
127            parent: Some(self),
128            languages: vec![],
129            build_compiler: None,
130        })
131    }
132}
133
134#[derive(Debug, Clone, Hash, PartialEq, Eq)]
135struct RustbookSrc<P: CommandLineStep> {
136    target: TargetSelection,
137    name: String,
138    src: PathBuf,
139    parent: Option<P>,
140    languages: Vec<&'static str>,
141    /// Compiler whose rustdoc should be used to document things using `mdbook-spec`.
142    build_compiler: Option<Compiler>,
143}
144
145impl<P: CommandLineStep> Step for RustbookSrc<P> {
146    type Output = ();
147
148    /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.
149    ///
150    /// This will not actually generate any documentation if the documentation has
151    /// already been generated.
152    fn run(self, builder: &Builder<'_>) {
153        let target = self.target;
154        let name = self.name;
155        let src = self.src;
156        let out = builder.doc_out(target);
157        t!(fs::create_dir_all(&out));
158
159        let out = out.join(&name);
160        let index = out.join("index.html");
161        let rustbook = builder.tool_exe(Tool::Rustbook);
162
163        if !builder.config.dry_run()
164            && (!up_to_date(&src, &index) || !up_to_date(&rustbook, &index))
165        {
166            builder.info(&format!("Rustbook ({target}) - {name}"));
167            let _ = fs::remove_dir_all(&out);
168
169            let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
170
171            if let Some(compiler) = self.build_compiler {
172                let mut rustdoc = builder.rustdoc_for_compiler(compiler);
173                rustdoc.pop();
174                let old_path = env::var_os("PATH").unwrap_or_default();
175                let new_path =
176                    env::join_paths(std::iter::once(rustdoc).chain(env::split_paths(&old_path)))
177                        .expect("could not add rustdoc to PATH");
178
179                rustbook_cmd.env("PATH", new_path);
180                builder.add_rustc_lib_path(compiler, &mut rustbook_cmd);
181            }
182
183            rustbook_cmd
184                .arg("build")
185                .arg(&src)
186                .arg("-d")
187                .arg(&out)
188                .arg("--rust-root")
189                .arg(&builder.src)
190                .run(builder);
191
192            for lang in &self.languages {
193                let out = out.join(lang);
194
195                builder.info(&format!("Rustbook ({target}) - {name} - {lang}"));
196                let _ = fs::remove_dir_all(&out);
197
198                builder
199                    .tool_cmd(Tool::Rustbook)
200                    .arg("build")
201                    .arg(&src)
202                    .arg("-d")
203                    .arg(&out)
204                    .arg("-l")
205                    .arg(lang)
206                    .run(builder);
207            }
208        }
209
210        if self.parent.is_some() {
211            builder.maybe_open_in_browser::<P>(index)
212        }
213    }
214
215    fn metadata(&self) -> Option<StepMetadata> {
216        let mut metadata = StepMetadata::doc(&format!("{} (book)", self.name), self.target);
217        if let Some(compiler) = self.build_compiler {
218            metadata = metadata.built_by(compiler);
219        }
220
221        Some(metadata)
222    }
223}
224
225#[derive(Debug, Clone, Hash, PartialEq, Eq)]
226pub struct TheBook {
227    /// Compiler whose rustdoc will be used to generated documentation.
228    build_compiler: Compiler,
229    target: TargetSelection,
230}
231
232impl CommandLineStep for TheBook {
233    type Output = ();
234
235    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
236        run.path("src/doc/book")
237    }
238
239    fn is_default_step(builder: &Builder<'_>) -> bool {
240        builder.config.docs
241    }
242
243    fn make_run(run: RunConfig<'_>) {
244        run.builder.ensure(TheBook {
245            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),
246            target: run.target,
247        });
248    }
249
250    /// Builds the book and associated stuff.
251    ///
252    /// We need to build:
253    ///
254    /// * Book
255    /// * Older edition redirects
256    /// * Version info and CSS
257    /// * Index page
258    /// * Redirect pages
259    fn run(self, builder: &Builder<'_>) {
260        builder.require_submodule("src/doc/book", None);
261
262        let build_compiler = self.build_compiler;
263        let target = self.target;
264
265        let absolute_path = builder.src.join("src/doc/book");
266        let redirect_path = absolute_path.join("redirects");
267
268        // build book
269        builder.ensure(RustbookSrc {
270            target,
271            name: "book".to_owned(),
272            src: absolute_path.clone(),
273            parent: Some(self),
274            languages: vec![],
275            build_compiler: None,
276        });
277
278        // building older edition redirects
279        for edition in &["first-edition", "second-edition", "2018-edition"] {
280            builder.ensure(RustbookSrc {
281                target,
282                name: format!("book/{edition}"),
283                src: absolute_path.join(edition),
284                // There should only be one book that is marked as the parent for each target, so
285                // treat the other editions as not having a parent.
286                parent: Option::<Self>::None,
287                languages: vec![],
288                build_compiler: None,
289            });
290        }
291
292        // build the version info page and CSS
293        let shared_assets = builder.ensure(SharedAssets { target });
294
295        // build the redirect pages
296        let _guard = builder.msg(Kind::Doc, "book redirect pages", None, build_compiler, target);
297        if builder.config.dry_run() {
298            return;
299        }
300
301        for file in t!(fs::read_dir(redirect_path)) {
302            let file = t!(file);
303            let path = file.path();
304            let path = path.to_str().unwrap();
305
306            invoke_rustdoc(builder, build_compiler, &shared_assets, target, path);
307        }
308    }
309}
310
311fn invoke_rustdoc(
312    builder: &Builder<'_>,
313    build_compiler: Compiler,
314    shared_assets: &SharedAssetsPaths,
315    target: TargetSelection,
316    markdown: &str,
317) {
318    let out = builder.doc_out(target);
319
320    let path = builder.src.join("src/doc").join(markdown);
321
322    let header = builder.src.join("src/doc/redirect.inc");
323    let footer = builder.src.join("src/doc/footer.inc");
324
325    let mut cmd = builder.rustdoc_cmd(build_compiler);
326
327    let out = out.join("book");
328
329    cmd.arg("--html-after-content")
330        .arg(&footer)
331        .arg("--html-before-content")
332        .arg(&shared_assets.version_info)
333        .arg("--html-in-header")
334        .arg(&header)
335        .arg("--markdown-no-toc")
336        .arg("--markdown-playground-url")
337        .arg("https://play.rust-lang.org/")
338        .arg("-o")
339        .arg(&out)
340        .arg(&path)
341        .arg("--markdown-css")
342        .arg("../rust.css")
343        .arg("-Zunstable-options");
344
345    if !builder.config.docs_minification {
346        cmd.arg("--disable-minification");
347    }
348
349    cmd.run(builder);
350}
351
352#[derive(Debug, Clone, Hash, PartialEq, Eq)]
353pub struct Standalone {
354    build_compiler: Compiler,
355    target: TargetSelection,
356}
357
358impl CommandLineStep for Standalone {
359    type Output = ();
360
361    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
362        run.path("src/doc").alias("standalone")
363    }
364
365    fn is_default_step(builder: &Builder<'_>) -> bool {
366        builder.config.docs
367    }
368
369    fn make_run(run: RunConfig<'_>) {
370        run.builder.ensure(Standalone {
371            build_compiler: prepare_doc_compiler(
372                run.builder,
373                run.builder.host_target,
374                run.builder.top_stage,
375            ),
376            target: run.target,
377        });
378    }
379
380    /// Generates all standalone documentation as compiled by the rustdoc in `stage`
381    /// for the `target` into `out`.
382    ///
383    /// This will list all of `src/doc` looking for markdown files and appropriately
384    /// perform transformations like substituting `VERSION`, `SHORT_HASH`, and
385    /// `STAMP` along with providing the various header/footer HTML we've customized.
386    ///
387    /// In the end, this is just a glorified wrapper around rustdoc!
388    fn run(self, builder: &Builder<'_>) {
389        let target = self.target;
390        let build_compiler = self.build_compiler;
391        let _guard = builder.msg(Kind::Doc, "standalone", None, build_compiler, target);
392        let out = builder.doc_out(target);
393        t!(fs::create_dir_all(&out));
394
395        let version_info = builder.ensure(SharedAssets { target: self.target }).version_info;
396
397        let favicon = builder.src.join("src/doc/favicon.inc");
398        let footer = builder.src.join("src/doc/footer.inc");
399        let full_toc = builder.src.join("src/doc/full-toc.inc");
400
401        for file in t!(fs::read_dir(builder.src.join("src/doc"))) {
402            let file = t!(file);
403            let path = file.path();
404            let filename = path.file_name().unwrap().to_str().unwrap();
405            if !filename.ends_with(".md") || filename == "README.md" {
406                continue;
407            }
408
409            let html = out.join(filename).with_extension("html");
410            let rustdoc = builder.rustdoc_for_compiler(build_compiler);
411            if up_to_date(&path, &html)
412                && up_to_date(&footer, &html)
413                && up_to_date(&favicon, &html)
414                && up_to_date(&full_toc, &html)
415                && (builder.config.dry_run() || up_to_date(&version_info, &html))
416                && (builder.config.dry_run() || up_to_date(&rustdoc, &html))
417            {
418                continue;
419            }
420
421            let mut cmd = builder.rustdoc_cmd(build_compiler);
422
423            cmd.arg("--html-after-content")
424                .arg(&footer)
425                .arg("--html-before-content")
426                .arg(&version_info)
427                .arg("--html-in-header")
428                .arg(&favicon)
429                .arg("--markdown-no-toc")
430                .arg("-Zunstable-options")
431                .arg("--index-page")
432                .arg(builder.src.join("src/doc/index.md"))
433                .arg("--markdown-playground-url")
434                .arg("https://play.rust-lang.org/")
435                .arg("-o")
436                .arg(&out)
437                .arg(&path);
438
439            if !builder.config.docs_minification {
440                cmd.arg("--disable-minification");
441            }
442
443            if filename == "not_found.md" {
444                cmd.arg("--markdown-css").arg("https://doc.rust-lang.org/rust.css");
445            } else {
446                cmd.arg("--markdown-css").arg("rust.css");
447            }
448            cmd.run(builder);
449        }
450
451        // We open doc/index.html as the default if invoked as `x.py doc --open`
452        // with no particular explicit doc requested (e.g. library/core).
453        if builder.paths.is_empty() || builder.was_invoked_explicitly::<Self>(Kind::Doc) {
454            let index = out.join("index.html");
455            builder.open_in_browser(index);
456        }
457    }
458
459    fn metadata(&self) -> Option<StepMetadata> {
460        Some(StepMetadata::doc("standalone", self.target).built_by(self.build_compiler))
461    }
462}
463
464#[derive(Debug, Clone, Hash, PartialEq, Eq)]
465pub struct Releases {
466    build_compiler: Compiler,
467    target: TargetSelection,
468}
469
470impl CommandLineStep for Releases {
471    type Output = ();
472
473    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
474        run.path("RELEASES.md").alias("releases")
475    }
476
477    fn is_default_step(builder: &Builder<'_>) -> bool {
478        builder.config.docs
479    }
480
481    fn make_run(run: RunConfig<'_>) {
482        run.builder.ensure(Releases {
483            build_compiler: prepare_doc_compiler(
484                run.builder,
485                run.builder.host_target,
486                run.builder.top_stage,
487            ),
488            target: run.target,
489        });
490    }
491
492    /// Generates HTML release notes to include in the final docs bundle.
493    ///
494    /// This uses the same stylesheet and other tools as Standalone, but the
495    /// RELEASES.md file is included at the root of the repository and gets
496    /// the headline added. In the end, the conversion is done by Rustdoc.
497    fn run(self, builder: &Builder<'_>) {
498        let target = self.target;
499        let build_compiler = self.build_compiler;
500        let _guard = builder.msg(Kind::Doc, "releases", None, build_compiler, target);
501        let out = builder.doc_out(target);
502        t!(fs::create_dir_all(&out));
503
504        builder.ensure(Standalone { build_compiler, target });
505
506        let version_info = builder.ensure(SharedAssets { target: self.target }).version_info;
507
508        let favicon = builder.src.join("src/doc/favicon.inc");
509        let footer = builder.src.join("src/doc/footer.inc");
510        let full_toc = builder.src.join("src/doc/full-toc.inc");
511
512        let html = out.join("releases.html");
513        let tmppath = out.join("releases.md");
514        let inpath = builder.src.join("RELEASES.md");
515        let rustdoc = builder.rustdoc_for_compiler(build_compiler);
516        if !up_to_date(&inpath, &html)
517            || !up_to_date(&footer, &html)
518            || !up_to_date(&favicon, &html)
519            || !up_to_date(&full_toc, &html)
520            || !(builder.config.dry_run()
521                || up_to_date(&version_info, &html)
522                || up_to_date(&rustdoc, &html))
523        {
524            let mut tmpfile = t!(fs::File::create(&tmppath));
525            t!(tmpfile.write_all(b"% Rust Release Notes\n\n"));
526            t!(io::copy(&mut t!(fs::File::open(&inpath)), &mut tmpfile));
527            mem::drop(tmpfile);
528            let mut cmd = builder.rustdoc_cmd(build_compiler);
529
530            cmd.arg("--html-after-content")
531                .arg(&footer)
532                .arg("--html-before-content")
533                .arg(&version_info)
534                .arg("--html-in-header")
535                .arg(&favicon)
536                .arg("--markdown-no-toc")
537                .arg("--markdown-css")
538                .arg("rust.css")
539                .arg("-Zunstable-options")
540                .arg("--index-page")
541                .arg(builder.src.join("src/doc/index.md"))
542                .arg("--markdown-playground-url")
543                .arg("https://play.rust-lang.org/")
544                .arg("-o")
545                .arg(&out)
546                .arg(&tmppath);
547
548            if !builder.config.docs_minification {
549                cmd.arg("--disable-minification");
550            }
551
552            cmd.run(builder);
553        }
554
555        // We open doc/RELEASES.html as the default if invoked as `x.py doc --open RELEASES.md`
556        // with no particular explicit doc requested (e.g. library/core).
557        if builder.was_invoked_explicitly::<Self>(Kind::Doc) {
558            builder.open_in_browser(&html);
559        }
560    }
561
562    fn metadata(&self) -> Option<StepMetadata> {
563        Some(StepMetadata::doc("releases", self.target).built_by(self.build_compiler))
564    }
565}
566
567#[derive(Debug, Clone)]
568pub struct SharedAssetsPaths {
569    pub version_info: PathBuf,
570}
571
572#[derive(Debug, Clone, Hash, PartialEq, Eq)]
573pub struct SharedAssets {
574    target: TargetSelection,
575}
576
577impl Step for SharedAssets {
578    type Output = SharedAssetsPaths;
579
580    /// Generate shared resources used by other pieces of documentation.
581    fn run(self, builder: &Builder<'_>) -> Self::Output {
582        let out = builder.doc_out(self.target);
583
584        let version_input = builder.src.join("src").join("doc").join("version_info.html.template");
585        let version_info = out.join("version_info.html");
586        if !builder.config.dry_run() && !up_to_date(&version_input, &version_info) {
587            let info = t!(fs::read_to_string(&version_input))
588                .replace("VERSION", &builder.rust_release())
589                .replace("SHORT_HASH", builder.rust_info().sha_short().unwrap_or(""))
590                .replace("STAMP", builder.rust_info().sha().unwrap_or(""));
591            t!(fs::write(&version_info, info));
592        }
593
594        builder.copy_link(
595            &builder.src.join("src").join("doc").join("rust.css"),
596            &out.join("rust.css"),
597            FileType::Regular,
598        );
599
600        builder.copy_link(
601            &builder
602                .src
603                .join("src")
604                .join("librustdoc")
605                .join("html")
606                .join("static")
607                .join("images")
608                .join("favicon.svg"),
609            &out.join("favicon.svg"),
610            FileType::Regular,
611        );
612        builder.copy_link(
613            &builder
614                .src
615                .join("src")
616                .join("librustdoc")
617                .join("html")
618                .join("static")
619                .join("images")
620                .join("favicon-32x32.png"),
621            &out.join("favicon-32x32.png"),
622            FileType::Regular,
623        );
624
625        SharedAssetsPaths { version_info }
626    }
627}
628
629/// Document the standard library using `build_compiler`.
630#[derive(Debug, Clone, Hash, PartialEq, Eq)]
631pub struct Std {
632    build_compiler: Compiler,
633    target: TargetSelection,
634    format: DocumentationFormat,
635    crates: Vec<String>,
636}
637
638impl Std {
639    pub(crate) fn from_build_compiler(
640        build_compiler: Compiler,
641        target: TargetSelection,
642        format: DocumentationFormat,
643    ) -> Self {
644        Std { build_compiler, target, format, crates: vec![] }
645    }
646}
647
648impl CommandLineStep for Std {
649    /// Path to a directory with the built documentation.
650    type Output = PathBuf;
651
652    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
653        run.crate_or_deps("sysroot").path("library")
654    }
655
656    fn is_default_step(builder: &Builder<'_>) -> bool {
657        builder.config.docs
658    }
659
660    fn make_run(run: RunConfig<'_>) {
661        let crates = compile::std_crates_for_make_run(&run);
662        let target_is_no_std = run.builder.no_std(run.target).unwrap_or(false);
663        if crates.is_empty() && target_is_no_std {
664            return;
665        }
666        run.builder.ensure(Std {
667            build_compiler: run.builder.compiler_for_std(run.builder.top_stage),
668            target: run.target,
669            format: if run.builder.config.cmd.json() {
670                DocumentationFormat::Json
671            } else {
672                DocumentationFormat::Html
673            },
674            crates,
675        });
676    }
677
678    /// Compile all standard library documentation.
679    ///
680    /// This will generate all documentation for the standard library and its
681    /// dependencies. This is largely just a wrapper around `cargo doc`.
682    fn run(self, builder: &Builder<'_>) -> Self::Output {
683        let target = self.target;
684        let crates = if self.crates.is_empty() {
685            builder
686                .in_tree_crates("sysroot", Some(target))
687                .iter()
688                .map(|c| c.name.to_string())
689                .collect()
690        } else {
691            self.crates
692        };
693
694        let out = match self.format {
695            DocumentationFormat::Html => builder.doc_out(target),
696            DocumentationFormat::Json => builder.json_doc_out(target),
697        };
698
699        t!(fs::create_dir_all(&out));
700
701        if self.format == DocumentationFormat::Html {
702            builder.ensure(SharedAssets { target: self.target });
703        }
704
705        let index_page = builder
706            .src
707            .join("src/doc/index.md")
708            .into_os_string()
709            .into_string()
710            .expect("non-utf8 paths are unsupported");
711        let mut extra_args = match self.format {
712            DocumentationFormat::Html => {
713                vec![
714                    "--markdown-css",
715                    "rust.css",
716                    "--markdown-no-toc",
717                    "--index-page",
718                    &index_page,
719                    "--generate-link-to-definition",
720                ]
721            }
722            DocumentationFormat::Json => vec![],
723        };
724
725        if !builder.config.docs_minification {
726            extra_args.push("--disable-minification");
727        }
728        // For `--index-page` and `--output-format=json`.
729        extra_args.push("-Zunstable-options");
730
731        let target_doc_dir_name =
732            if self.format == DocumentationFormat::Json { "json-doc" } else { "doc" };
733        let target_dir = builder
734            .stage_out(self.build_compiler, Mode::Std)
735            .join(target)
736            .join(target_doc_dir_name);
737
738        // This is directory where the compiler will place the output of the command.
739        // We will then copy the files from this directory into the final `out` directory, the specified
740        // as a function parameter.
741        let out_dir = target_dir.join(target).join("doc");
742
743        let mut cargo = doc_std(
744            builder,
745            self.format,
746            self.build_compiler,
747            target,
748            &target_dir,
749            &extra_args,
750            &crates,
751        );
752        match self.format {
753            DocumentationFormat::Html => {}
754            DocumentationFormat::Json => {
755                // We have to pass these directly to cargo, rather than through RUSTDOCFLAGS,
756                // otherwise Cargo will not detect freshness of the output correctly, and keep
757                // rebuilding the docs on every invocation.
758                cargo.args(["-Zunstable-options", "--output-format", "json"]);
759            }
760        }
761
762        let description =
763            format!("library{} in {} format", crate_description(&crates), self.format.as_str());
764
765        {
766            let _guard =
767                builder.msg(Kind::Doc, description, Mode::Std, self.build_compiler, target);
768
769            cargo.into_cmd().run(builder);
770            builder.cp_link_r(&out_dir, &out);
771        }
772
773        // Open if the format is HTML
774        if let DocumentationFormat::Html = self.format {
775            if builder.paths.iter().any(|path| path.ends_with("library")) {
776                // For `x.py doc library --open`, open `std` by default.
777                let index = out.join("std").join("index.html");
778                builder.maybe_open_in_browser::<Self>(index);
779            } else {
780                for requested_crate in crates {
781                    if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) {
782                        let index = out.join(requested_crate).join("index.html");
783                        builder.maybe_open_in_browser::<Self>(index);
784                        break;
785                    }
786                }
787            }
788        }
789
790        out
791    }
792
793    fn metadata(&self) -> Option<StepMetadata> {
794        Some(
795            StepMetadata::doc("std", self.target)
796                .built_by(self.build_compiler)
797                .with_metadata(format!("crates=[{}]", self.crates.join(","))),
798        )
799    }
800}
801
802/// Name of the crates that are visible to consumers of the standard library.
803/// Documentation for internal crates is handled by the rustc step, so internal crates will show
804/// up there.
805///
806/// Order here is important!
807/// Crates need to be processed starting from the leaves, otherwise rustdoc will not
808/// create correct links between crates because rustdoc depends on the
809/// existence of the output directories to know if it should be a local
810/// or remote link.
811const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"];
812
813#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
814pub enum DocumentationFormat {
815    Html,
816    Json,
817}
818
819impl DocumentationFormat {
820    fn as_str(&self) -> &str {
821        match self {
822            DocumentationFormat::Html => "HTML",
823            DocumentationFormat::Json => "JSON",
824        }
825    }
826}
827
828/// Prepare a Cargo command for building the documentation for public standard library crates.
829fn doc_std(
830    builder: &Builder<'_>,
831    format: DocumentationFormat,
832    build_compiler: Compiler,
833    target: TargetSelection,
834    target_dir: &Path,
835    extra_args: &[&str],
836    requested_crates: &[String],
837) -> builder::Cargo {
838    let mut cargo = builder::Cargo::new(
839        builder,
840        build_compiler,
841        Mode::Std,
842        SourceType::InTree,
843        target,
844        Kind::Doc,
845    );
846
847    compile::std_cargo(builder, target, &mut cargo, requested_crates);
848    cargo
849        .arg("--no-deps")
850        .arg("--target-dir")
851        .arg(&*target_dir.to_string_lossy())
852        .arg("-Zrustdoc-map")
853        .rustdocflag("--extern-html-root-url")
854        .rustdocflag("std_detect=https://docs.rs/std_detect/latest/")
855        .rustdocflag("--extern-html-root-takes-precedence")
856        .rustdocflag("--resource-suffix")
857        .rustdocflag(&builder.version);
858    for arg in extra_args {
859        cargo.rustdocflag(arg);
860    }
861
862    // This is needed for cargo-semver-checks and potentially other downstream tools that consume
863    // the JSON data.
864    if format == DocumentationFormat::Json || builder.config.library_docs_private_items {
865        cargo.rustdocflag("--document-private-items").rustdocflag("--document-hidden-items");
866    }
867    cargo
868}
869
870/// Prepare a compiler that will be able to document something for `target` at `stage`.
871pub fn prepare_doc_compiler(
872    builder: &Builder<'_>,
873    target: TargetSelection,
874    stage: u32,
875) -> Compiler {
876    assert!(stage > 0, "Cannot document anything in stage 0");
877    let build_compiler = builder.compiler(stage - 1, builder.host_target);
878    builder.std(build_compiler, target);
879    build_compiler
880}
881
882/// Run rustdoc to merge cross-crate info metadata (like the search index) from individual
883/// executions of rustdoc into `out_dir`.
884/// The `json_files` parameter should contain paths to JSON file artifacts generated by previous
885/// executions of `cargo doc`.
886fn merge_rustdoc_cci(
887    builder: &Builder<'_>,
888    build_compiler: Compiler,
889    json_files: &[PathBuf],
890    out_dir: &Path,
891) {
892    let mut cmd = builder.rustdoc_cmd(build_compiler);
893
894    cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(out_dir);
895
896    if !builder.config.docs_minification {
897        cmd.arg("--disable-minification");
898    }
899
900    for json_file in json_files {
901        cmd.arg("--read-doc-meta-dir").arg(json_file.parent().unwrap());
902    }
903
904    cmd.run(builder);
905}
906
907/// Generate the combined compiler + tools docs for a given toolchain.
908/// This contains both the compiler docs, docs of rustc_private tools (miri, clippy, etc.), cargo
909/// and also some bootstrap related tools (bootstrap itself, compiletest, tidy, etc.).
910///
911/// It gets hosted at https://doc.rust-lang.org/nightly/nightly-rustc/index.html.
912///
913/// Compiler documentation is distributed separately, so we make sure
914/// we do not merge it with the other documentation from std, test and
915/// proc_macros. This is largely just a wrapper around `cargo doc`.
916///
917/// Returns a path to a directory with the generated documentation.
918#[derive(Debug, Clone, Hash, PartialEq, Eq)]
919pub struct CompilerWithTools {
920    build_compiler: Compiler,
921    target: TargetSelection,
922    stage: u32,
923}
924
925impl CompilerWithTools {
926    /// Document `stage` compiler for the given `target`.
927    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
928        let build_compiler = prepare_doc_compiler(builder, target, stage);
929        Self { build_compiler, target, stage }
930    }
931}
932
933impl CommandLineStep for CompilerWithTools {
934    type Output = PathBuf;
935    const IS_HOST: bool = true;
936
937    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
938        run.alias("compiler-with-tools")
939    }
940
941    fn is_default_step(builder: &Builder<'_>) -> bool {
942        builder.config.compiler_docs
943    }
944
945    fn make_run(run: RunConfig<'_>) {
946        run.builder.ensure(CompilerWithTools::for_stage(
947            run.builder,
948            run.builder.top_stage,
949            run.target,
950        ));
951    }
952
953    fn run(self, builder: &Builder<'_>) -> Self::Output {
954        let CompilerWithTools { target, build_compiler, stage } = self;
955
956        // This is the intended out directory for combined compiler documentation.
957        let out = builder.compiler_doc_out(target);
958        let _ = fs::remove_dir_all(&out);
959
960        let _guard =
961            builder.msg(Kind::Doc, "compiler-with-tools", Mode::Rustc, build_compiler, target);
962
963        let combined_docs = vec![
964            builder.ensure(Rustc::for_stage(builder, stage, target)),
965            builder.ensure(Rustdoc::new(builder, target)),
966            builder.ensure(Rustfmt::new(builder, target)),
967            builder.ensure(Clippy::new(builder, target)),
968            builder.ensure(Miri::new(builder, target)),
969            builder.ensure(Cargo::new(builder, target)),
970            builder.ensure(Tidy::new(builder, target)),
971            builder.ensure(Bootstrap::new(builder, target)),
972            builder.ensure(BuildHelper::new(builder, target)),
973            builder.ensure(Compiletest::new(builder, target)),
974            builder.ensure(RunMakeSupport::new(builder, target)),
975        ];
976
977        if !builder.config.dry_run() {
978            // Now copy all the individual docs into a single directory
979            let mut json_files = vec![];
980            for docs in combined_docs {
981                json_files.extend(docs.artifacts.json_files);
982
983                // Doc directories to link to the shared output directory
984                // We add the host doc dirs, which should already be symlinked in the target
985                // docs dir at this point (see `merge_host_and_target_docs`).
986                let dirs_to_copy: Vec<_> = docs
987                    .artifacts
988                    .target_dirs
989                    .iter()
990                    .chain(docs.artifacts.host_dirs.iter())
991                    .map(|d| d.file_name().unwrap().to_str().unwrap())
992                    .collect();
993                for dir in dirs_to_copy {
994                    // Link the docs dir
995                    let docs_dir = docs.out_dir.join(dir);
996                    assert!(docs_dir.exists(), "Docs directory {docs_dir:?} does not exist.");
997                    let out_docs_dir = out.join(dir);
998                    builder.create_dir(&out_docs_dir);
999                    builder.cp_link_r(&docs_dir, &out_docs_dir);
1000
1001                    // And the src dir
1002                    let src_dir = docs.out_dir.join("src").join(dir);
1003                    assert!(src_dir.exists(), "Docs source directory {src_dir:?} does not exist.");
1004                    let out_src_dir = out.join("src").join(dir);
1005                    builder.create_dir(&out_src_dir);
1006                    builder.cp_link_r(&src_dir, &out_src_dir);
1007                }
1008            }
1009            // And finally merge all the CCI metadata
1010            merge_rustdoc_cci(builder, build_compiler, &json_files, &out);
1011        }
1012
1013        // Handle `--open`.
1014        builder.open_in_browser(out.join("index.html"));
1015        out
1016    }
1017
1018    fn metadata(&self) -> Option<StepMetadata> {
1019        Some(StepMetadata::doc("CompilerWithTools", self.target).built_by(self.build_compiler))
1020    }
1021}
1022
1023/// Output of a Doc step.
1024#[derive(Clone)]
1025pub struct BuiltDocs {
1026    /// Target doc directory with the generated documentation.
1027    out_dir: PathBuf,
1028    /// Doc artifacts gathered from Cargo during the doc build.
1029    artifacts: DocArtifacts,
1030}
1031
1032/// Document the compiler for the given `target` using rustdoc from `build_compiler`.
1033///
1034/// Return the path to the generated rustc documentation directory.
1035#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1036pub struct Rustc {
1037    build_compiler: Compiler,
1038    target: TargetSelection,
1039    crates: Vec<String>,
1040}
1041
1042impl Rustc {
1043    /// Document `stage` compiler for the given `target`.
1044    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
1045        let build_compiler = prepare_doc_compiler(builder, target, stage);
1046        Self::from_build_compiler(builder, build_compiler, target)
1047    }
1048
1049    fn from_build_compiler(
1050        builder: &Builder<'_>,
1051        build_compiler: Compiler,
1052        target: TargetSelection,
1053    ) -> Self {
1054        let crates = builder
1055            .in_tree_crates("rustc-main", Some(target))
1056            .into_iter()
1057            .map(|krate| krate.name.to_string())
1058            .collect();
1059        Self { build_compiler, target, crates }
1060    }
1061}
1062
1063impl CommandLineStep for Rustc {
1064    type Output = BuiltDocs;
1065    const IS_HOST: bool = true;
1066
1067    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1068        run.crate_or_deps("rustc-main").path("compiler")
1069    }
1070
1071    fn is_default_step(builder: &Builder<'_>) -> bool {
1072        builder.config.compiler_docs
1073    }
1074
1075    fn make_run(run: RunConfig<'_>) {
1076        run.builder.ensure(Rustc::for_stage(run.builder, run.builder.top_stage, run.target));
1077    }
1078
1079    /// Generates compiler documentation.
1080    ///
1081    /// This will generate all documentation for compiler and dependencies.
1082    /// Compiler documentation is distributed separately, so we make sure
1083    /// we do not merge it with the other documentation from std, test and
1084    /// proc_macros. This is largely just a wrapper around `cargo doc`.
1085    fn run(self, builder: &Builder<'_>) -> Self::Output {
1086        let target = self.target;
1087
1088        // Build the standard library, so that proc-macros can use it.
1089        // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.)
1090        let build_compiler = self.build_compiler;
1091        builder.std(build_compiler, builder.config.host_target);
1092
1093        let _guard = builder.msg(
1094            Kind::Doc,
1095            format!("compiler{}", crate_description(&self.crates)),
1096            Mode::Rustc,
1097            build_compiler,
1098            target,
1099        );
1100
1101        // Build cargo command.
1102        let mut cargo = builder::Cargo::new(
1103            builder,
1104            build_compiler,
1105            Mode::Rustc,
1106            SourceType::InTree,
1107            target,
1108            Kind::Doc,
1109        );
1110
1111        cargo.rustdocflag("--document-private-items");
1112        // Since we always pass --document-private-items, there's no need to warn about linking to private items.
1113        cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
1114        cargo.rustdocflag("--enable-index-page");
1115        cargo.rustdocflag("-Znormalize-docs");
1116        cargo.rustdocflag("--show-type-layout");
1117        // FIXME: `--generate-link-to-definition` tries to resolve cfged out code
1118        // see https://github.com/rust-lang/rust/pull/122066#issuecomment-1983049222
1119        // If there is any bug, please comment out the next line.
1120        cargo.rustdocflag("--generate-link-to-definition");
1121        cargo.rustdocflag("--generate-macro-expansion");
1122
1123        compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
1124
1125        // Only include compiler crates, no dependencies of those, such as `libc`.
1126        // Do link to dependencies on `docs.rs` however using `rustdoc-map`.
1127        cargo.arg("--no-deps");
1128        cargo.arg("-Zrustdoc-map");
1129
1130        // FIXME: `-Zrustdoc-map` does not yet correctly work for transitive dependencies,
1131        // once this is no longer an issue the special case for `ena` can be removed.
1132        cargo.rustdocflag("--extern-html-root-url");
1133        cargo.rustdocflag("ena=https://docs.rs/ena/latest/");
1134
1135        let cargo_target_dir = builder.stage_out(build_compiler, Mode::Rustc);
1136        let target_doc_dir = cargo_target_dir.join(target).join("doc");
1137        let host_doc_dir = cargo_target_dir.join("doc");
1138        for krate in &*self.crates {
1139            // Create all crate output directories first to make sure rustdoc uses
1140            // relative links.
1141            // FIXME: Cargo should probably do this itself.
1142            let dir_name = normalize_doc_crate_name(krate);
1143            t!(fs::create_dir_all(target_doc_dir.join(&*dir_name)));
1144            cargo.arg("-p").arg(krate);
1145        }
1146
1147        let artifacts = create_docs_and_gather_artifacts(builder, cargo);
1148        artifacts.sanity_check_crates(builder, self.crates.iter());
1149
1150        if !builder.config.dry_run() {
1151            merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir);
1152            merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir);
1153        }
1154
1155        // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md`
1156        // with no particular explicit doc requested (e.g. library/core).
1157        if builder.was_invoked_explicitly::<Self>(Kind::Doc) {
1158            let index = if builder.paths.iter().any(|path| path.ends_with("compiler")) {
1159                // For `x.py doc compiler --open`, open `rustc_middle` by default.
1160                target_doc_dir.join("rustc_middle").join("index.html")
1161            } else if let Some(krate) = self.crates.first() {
1162                // Let's open the first crate documentation page:
1163                target_doc_dir.join(normalize_doc_crate_name(krate)).join("index.html")
1164            } else {
1165                target_doc_dir.clone()
1166            };
1167            builder.open_in_browser(index);
1168        }
1169
1170        BuiltDocs { out_dir: target_doc_dir, artifacts }
1171    }
1172
1173    fn metadata(&self) -> Option<StepMetadata> {
1174        Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler))
1175    }
1176}
1177
1178/// Stores generated documentation artifacts.
1179#[derive(Clone, Debug)]
1180struct DocArtifacts {
1181    /// Directories with HTML docs for host (proc-macro) crates.
1182    host_dirs: Vec<PathBuf>,
1183    /// Directories with HTML docs for target crates.
1184    target_dirs: Vec<PathBuf>,
1185    /// JSON files used to create the final CCI index
1186    json_files: Vec<PathBuf>,
1187}
1188
1189impl DocArtifacts {
1190    /// Ensure that all passed crates were documented.
1191    fn sanity_check_crates<S>(&self, builder: &Builder<'_>, crates: impl Iterator<Item = S>)
1192    where
1193        S: AsRef<str>,
1194    {
1195        if builder.config.dry_run() {
1196            return;
1197        }
1198        let crate_names: HashSet<&str> = self
1199            .host_dirs
1200            .iter()
1201            .chain(self.target_dirs.iter())
1202            .filter_map(|d| d.file_name().and_then(|d| d.to_str()))
1203            .collect();
1204        for krate in crates {
1205            let krate = krate.as_ref();
1206            let krate = normalize_doc_crate_name(krate);
1207            if !crate_names.contains(krate.as_str()) {
1208                eprintln!("ERROR: crate {krate} was not documented!");
1209                exit_process(1);
1210            }
1211        }
1212    }
1213}
1214
1215/// Run `cargo doc` and gather generated documentation artifacts.
1216fn create_docs_and_gather_artifacts(builder: &Builder<'_>, cargo: builder::Cargo) -> DocArtifacts {
1217    let mut json_files = vec![];
1218    let mut host_dirs = vec![];
1219    let mut target_dirs = vec![];
1220    stream_cargo(builder, cargo, vec![], &mut |msg| {
1221        let CargoMessage::CompilerArtifact { filenames, target } = msg else {
1222            return;
1223        };
1224        if !target.doc {
1225            return;
1226        }
1227        // Note: An alternative way to check host docs would be to check whether the generated
1228        // output is a child of the host doc directory (which we would have to pass to this
1229        // function).
1230        let is_host = target.crate_types.iter().any(|t| t == "proc-macro");
1231        for filename in filenames {
1232            let path = Path::new(filename.as_ref());
1233            let Some(extension) = path.extension().and_then(|ext| ext.to_str()) else {
1234                continue;
1235            };
1236            let path = path.to_path_buf();
1237            if extension == "json" {
1238                json_files.push(path.to_path_buf());
1239            } else if extension == "html" {
1240                if is_host {
1241                    // doc/<crate>/index.html -> doc/<crate>
1242                    host_dirs.push(path.parent().unwrap().to_path_buf());
1243                } else {
1244                    target_dirs.push(path.parent().unwrap().to_path_buf());
1245                }
1246            }
1247        }
1248    });
1249    DocArtifacts { host_dirs, target_dirs, json_files }
1250}
1251
1252/// Merge host and target documentation for a set of crates.
1253/// We pass `--target` when documenting, so Cargo will put the built documentation into two places:
1254/// - `target/doc` - contains documentation of host code, so proc macros
1255/// - `target/<target>/doc` - contains documentation of "normal" code
1256///
1257/// See https://github.com/rust-lang/cargo/issues/7677.
1258///
1259/// To produce a single unified documentation, we want to merge them together.
1260/// We do that by creating symlinks into the target doc dir that will point to the host doc
1261/// directories.
1262/// The target doc directory will then contain the combined docs.
1263fn merge_host_and_target_docs(
1264    builder: &Builder<'_>,
1265    docs: &DocArtifacts,
1266    host_doc_dir: &Path,
1267    target_doc_dir: &Path,
1268) {
1269    // Sanity check that there is no host/target overlap
1270    for dir in &docs.host_dirs {
1271        let name = dir.file_name().and_then(|d| d.to_str()).unwrap();
1272        if let Some(target_dir) = docs.target_dirs.iter().find_map(|d| {
1273            let dirname = d.file_name().and_then(|d| d.to_str())?;
1274            if dirname == name { Some(d) } else { None }
1275        }) {
1276            eprintln!(
1277                "ERROR: host docs directory `{name}` ({dir:?}) is also contained in target doc directory ({target_dir:?})"
1278            );
1279            exit_process(1);
1280        }
1281    }
1282
1283    let target_src_dir = target_doc_dir.join("src");
1284    let host_src_dir = host_doc_dir.join("src");
1285
1286    // Ideally, we would remove all previous symlinks here.
1287    // However, some of the tools actually share the same build docs directory, so we shouldn't do
1288    // that, otherwise they will invalidate one another.
1289
1290    for host_docs_crate in &docs.host_dirs {
1291        let dir_name = host_docs_crate.file_name().unwrap().to_str().unwrap();
1292        // Normalize crate name
1293        let dir_name = normalize_doc_crate_name(dir_name);
1294
1295        t!(symlink_dir(&builder.config, host_docs_crate, &target_doc_dir.join(&dir_name)));
1296
1297        // Also symlink its source directory
1298        let target_src_out = target_src_dir.join(&dir_name);
1299        let host_src_out = host_src_dir.join(&dir_name);
1300        t!(symlink_dir(&builder.config, &host_src_out, &target_src_out));
1301    }
1302
1303    // Sanity check that all directories contain some documentation
1304    for dir in docs.target_dirs.iter().chain(docs.host_dirs.iter()) {
1305        // Making sure the directory exists and is not empty.
1306        assert!(dir.exists(), "Doc directory {dir:?} does not exist");
1307        assert!(t!(dir.read_dir()).next().is_some(), "Doc directory {dir:?} is empty");
1308    }
1309}
1310
1311/// Normalizes crate name to get a name that is used to generate documentation on disk.
1312/// Turns `rustc-main` into `rustc_main`.
1313fn normalize_doc_crate_name(name: &str) -> String {
1314    name.replace("-", "_")
1315}
1316
1317macro_rules! tool_doc {
1318    (
1319        $tool: ident,
1320        $path: literal,
1321        mode = $mode:expr
1322        $(, is_library = $is_library:expr )?
1323        $(, crates = $crates:expr )?
1324        // Subset of nightly features that are allowed to be used when documenting
1325        $(, allow_features: $allow_features:expr )?
1326       ) => {
1327        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
1328        pub struct $tool {
1329            build_compiler: Compiler,
1330            mode: Mode,
1331            target: TargetSelection,
1332        }
1333
1334        impl $tool {
1335            fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool {
1336                let build_compiler = match $mode {
1337                    Mode::ToolRustcPrivate => {
1338                        // Rustdoc needs the rustc sysroot available to build.
1339                        let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target);
1340
1341                        // Build rustc docs so that we generate relative links.
1342                        builder.ensure(Rustc::from_build_compiler(builder, compilers.build_compiler(), target));
1343                        compilers.build_compiler()
1344                    }
1345                    Mode::ToolTarget => {
1346                        // when shipping multiple docs together in one folder,
1347                        // they all need to use the same rustdoc version
1348                        prepare_doc_compiler(builder, builder.host_target, builder.top_stage)
1349                    }
1350                    _ => {
1351                        panic!("Unexpected tool mode for documenting: {:?}", $mode);
1352                    }
1353                };
1354                $tool { build_compiler, mode: $mode, target }
1355            }
1356            fn crates() -> &'static [&'static str] {
1357                &$($crates)?[..]
1358            }
1359        }
1360
1361        impl CommandLineStep for $tool {
1362            type Output = BuiltDocs;
1363            const IS_HOST: bool = true;
1364
1365            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1366                run.path($path)
1367            }
1368
1369            fn is_default_step(builder: &Builder<'_>) -> bool {
1370                builder.config.compiler_docs
1371            }
1372
1373            fn make_run(run: RunConfig<'_>) {
1374                run.builder.ensure($tool::new(run.builder, run.target));
1375            }
1376
1377            /// Generates documentation for a tool.
1378            ///
1379            /// This is largely just a wrapper around `cargo doc`.
1380            fn run(self, builder: &Builder<'_>) -> Self::Output {
1381                let mut source_type = SourceType::InTree;
1382
1383                if let Some(submodule_path) = submodule_path_of(&builder, $path) {
1384                    source_type = SourceType::Submodule;
1385                    builder.require_submodule(&submodule_path, None);
1386                }
1387
1388                let $tool { build_compiler, mode, target } = self;
1389
1390                // Build cargo command.
1391                let mut cargo = prepare_tool_cargo(
1392                    builder,
1393                    build_compiler,
1394                    mode,
1395                    target,
1396                    Kind::Doc,
1397                    $path,
1398                    source_type,
1399                    &[],
1400                );
1401                let allow_features = {
1402                    let mut _value = "";
1403                    $( _value = $allow_features; )?
1404                    _value
1405                };
1406
1407                if !allow_features.is_empty() {
1408                    cargo.allow_features(allow_features);
1409                }
1410
1411                // Only include compiler crates, no dependencies of those, such as `libc`.
1412                cargo.arg("--no-deps");
1413
1414                if false $(|| $is_library)? {
1415                    cargo.arg("--lib");
1416                }
1417
1418                for krate in $tool::crates() {
1419                    cargo.arg("-p").arg(krate);
1420                }
1421
1422                cargo.rustdocflag("--document-private-items");
1423                // Since we always pass --document-private-items, there's no need to warn about linking to private items.
1424                cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
1425                cargo.rustdocflag("--enable-index-page");
1426                cargo.rustdocflag("--show-type-layout");
1427                cargo.rustdocflag("--generate-link-to-definition");
1428
1429                let cargo_target_dir = builder.stage_out(build_compiler, mode);
1430                let target_doc_dir = cargo_target_dir.join(target).join("doc");
1431                let host_doc_dir = cargo_target_dir.join("doc");
1432                for krate in $tool::crates() {
1433                    let dir_name = normalize_doc_crate_name(krate);
1434                    t!(fs::create_dir_all(target_doc_dir.join(&*dir_name)));
1435                }
1436
1437                let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target);
1438                let artifacts = create_docs_and_gather_artifacts(builder, cargo);
1439                artifacts.sanity_check_crates(builder, $tool::crates().iter());
1440
1441                if !builder.config.dry_run() {
1442                    merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir);
1443                    merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir);
1444                }
1445                BuiltDocs { out_dir: target_doc_dir, artifacts }
1446            }
1447
1448            fn metadata(&self) -> Option<StepMetadata> {
1449                Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler))
1450            }
1451        }
1452    }
1453}
1454
1455// NOTE: make sure to register these in `Builder::get_step_description`.
1456tool_doc!(
1457    BuildHelper,
1458    "src/build_helper",
1459    // ideally, this would use ToolBootstrap,
1460    // but we distribute these docs together in the same folder
1461    // as a bunch of stage1 tools, and you can't mix rustdoc versions
1462    // because that breaks cross-crate data (particularly search)
1463    mode = Mode::ToolTarget,
1464    is_library = true,
1465    crates = ["build_helper"]
1466);
1467tool_doc!(
1468    Rustdoc,
1469    "src/tools/rustdoc",
1470    mode = Mode::ToolRustcPrivate,
1471    crates = ["rustdoc", "rustdoc-json-types"]
1472);
1473tool_doc!(
1474    Rustfmt,
1475    "src/tools/rustfmt",
1476    mode = Mode::ToolRustcPrivate,
1477    crates = ["rustfmt-nightly", "rustfmt-config_proc_macro"]
1478);
1479tool_doc!(
1480    Clippy,
1481    "src/tools/clippy",
1482    mode = Mode::ToolRustcPrivate,
1483    crates = ["clippy_config", "clippy_utils"]
1484);
1485tool_doc!(Miri, "src/tools/miri", mode = Mode::ToolRustcPrivate, crates = ["miri"]);
1486tool_doc!(
1487    Cargo,
1488    "src/tools/cargo",
1489    mode = Mode::ToolTarget,
1490    crates = [
1491        "cargo",
1492        "cargo-credential",
1493        "cargo-platform",
1494        "cargo-test-macro",
1495        "cargo-test-support",
1496        "cargo-util",
1497        "cargo-util-schemas",
1498        "crates-io",
1499        "mdman",
1500        "rustfix",
1501    ],
1502    // Required because of the im-rc dependency of Cargo, which automatically opts into the
1503    // "specialization" feature in its build script when it detects a nightly toolchain.
1504    allow_features: "specialization"
1505);
1506tool_doc!(Tidy, "src/tools/tidy", mode = Mode::ToolTarget, crates = ["tidy"]);
1507tool_doc!(
1508    Bootstrap,
1509    "src/bootstrap",
1510    mode = Mode::ToolTarget,
1511    is_library = true,
1512    crates = ["bootstrap"]
1513);
1514tool_doc!(
1515    RunMakeSupport,
1516    "src/tools/run-make-support",
1517    mode = Mode::ToolTarget,
1518    is_library = true,
1519    crates = ["run_make_support"]
1520);
1521tool_doc!(
1522    Compiletest,
1523    "src/tools/compiletest",
1524    mode = Mode::ToolTarget,
1525    is_library = true,
1526    crates = ["compiletest"]
1527);
1528
1529#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1530pub struct ErrorIndex {
1531    compilers: RustcPrivateCompilers,
1532}
1533
1534impl CommandLineStep for ErrorIndex {
1535    type Output = ();
1536    const IS_HOST: bool = true;
1537
1538    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1539        run.path("src/tools/error_index_generator")
1540    }
1541
1542    fn is_default_step(builder: &Builder<'_>) -> bool {
1543        builder.config.docs
1544    }
1545
1546    fn make_run(run: RunConfig<'_>) {
1547        run.builder.ensure(ErrorIndex {
1548            compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target),
1549        });
1550    }
1551
1552    /// Generates the HTML rendered error-index by running the
1553    /// `error_index_generator` tool.
1554    fn run(self, builder: &Builder<'_>) {
1555        builder.info(&format!("Documenting error index ({})", self.compilers.target()));
1556        let out = builder.doc_out(self.compilers.target());
1557        t!(fs::create_dir_all(&out));
1558        tool::ErrorIndex::command(builder, self.compilers)
1559            .arg("html")
1560            .arg(&out)
1561            .arg(&builder.version)
1562            .run(builder);
1563
1564        let index = out.join("error-index.html");
1565        builder.maybe_open_in_browser::<Self>(index);
1566    }
1567
1568    fn metadata(&self) -> Option<StepMetadata> {
1569        Some(
1570            StepMetadata::doc("error-index", self.compilers.target())
1571                .built_by(self.compilers.build_compiler()),
1572        )
1573    }
1574}
1575
1576/// Runs the `unstable-book-gen` tool and returns a path to the generate unstable book markdown
1577/// files.
1578#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1579pub struct UnstableBookGen {
1580    build_compiler: Compiler,
1581    target: TargetSelection,
1582}
1583
1584impl CommandLineStep for UnstableBookGen {
1585    type Output = PathBuf;
1586    const IS_HOST: bool = true;
1587
1588    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1589        run.path("src/tools/unstable-book-gen")
1590    }
1591
1592    fn is_default_step(builder: &Builder<'_>) -> bool {
1593        builder.config.docs
1594    }
1595
1596    fn make_run(run: RunConfig<'_>) {
1597        run.builder.ensure(UnstableBookGen {
1598            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),
1599            target: run.target,
1600        });
1601    }
1602
1603    fn run(self, builder: &Builder<'_>) -> Self::Output {
1604        let target = self.target;
1605        let rustc_path = builder.rustc(self.build_compiler);
1606
1607        builder.info(&format!("Generating unstable book md files ({target})"));
1608        let out = builder.out.join(target).join("md-doc").join("unstable-book");
1609        builder.create_dir(&out);
1610        builder.remove_dir(&out);
1611        let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
1612        cmd.arg(builder.src.join("library"));
1613        cmd.arg(builder.src.join("compiler"));
1614        cmd.arg(builder.src.join("src"));
1615        cmd.arg(rustc_path);
1616        cmd.arg(&out);
1617
1618        // Running rustc requires the library path if rust.rpath = false
1619        // or any other libraries are in a custom location.
1620        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
1621
1622        cmd.run(builder);
1623        out
1624    }
1625}
1626
1627/// Builds the Rust compiler book.
1628#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1629pub struct RustcBook {
1630    build_compiler: Compiler,
1631    target: TargetSelection,
1632    /// Test that the examples of lints in the book produce the correct lints in the expected
1633    /// format.
1634    validate: bool,
1635}
1636
1637impl RustcBook {
1638    pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self {
1639        Self { build_compiler, target, validate: true }
1640    }
1641}
1642
1643impl CommandLineStep for RustcBook {
1644    type Output = ();
1645    const IS_HOST: bool = true;
1646
1647    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1648        run.path("src/doc/rustc")
1649    }
1650
1651    fn is_default_step(builder: &Builder<'_>) -> bool {
1652        builder.config.docs
1653    }
1654
1655    fn make_run(run: RunConfig<'_>) {
1656        // Bump the stage to 2, because the rustc book requires an in-tree compiler.
1657        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1658        // in stage 1.
1659        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1660            run.builder.top_stage
1661        } else {
1662            2
1663        };
1664
1665        run.builder.ensure(RustcBook {
1666            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1667            target: run.target,
1668            validate: false,
1669        });
1670    }
1671
1672    /// Builds the rustc book.
1673    ///
1674    /// The lints are auto-generated by a tool, and then merged into the book
1675    /// in the "md-doc" directory in the build output directory. Then
1676    /// "rustbook" is used to convert it to HTML.
1677    fn run(self, builder: &Builder<'_>) {
1678        let out_base = builder.out.join(self.target).join("md-doc").join("rustc");
1679        t!(fs::create_dir_all(&out_base));
1680        let out_listing = out_base.join("src/lints");
1681        builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base);
1682        builder.info(&format!("Generating lint docs ({})", self.target));
1683
1684        let rustc = builder.rustc(self.build_compiler);
1685        // The tool runs `rustc` for extracting output examples, so it needs a
1686        // functional sysroot.
1687        builder.std(self.build_compiler, self.target);
1688        let mut cmd = builder.tool_cmd(Tool::LintDocs);
1689        cmd.arg("--build-rustc-stage");
1690        cmd.arg(self.build_compiler.stage.to_string());
1691        cmd.arg("--src");
1692        cmd.arg(builder.src.join("compiler"));
1693        cmd.arg("--out");
1694        cmd.arg(&out_listing);
1695        cmd.arg("--rustc");
1696        cmd.arg(&rustc);
1697        cmd.arg("--rustc-target").arg(self.target.rustc_target_arg());
1698        if let Some(target_linker) = builder.linker(self.target) {
1699            cmd.arg("--rustc-linker").arg(target_linker);
1700        }
1701        if builder.is_verbose() {
1702            cmd.arg("--verbose");
1703        }
1704        if self.validate {
1705            cmd.arg("--validate");
1706        }
1707        // We need to validate nightly features, even on the stable channel.
1708        // Set this unconditionally as the stage0 compiler may be being used to
1709        // document.
1710        cmd.env("RUSTC_BOOTSTRAP", "1");
1711
1712        // If the lib directories are in an unusual location (changed in
1713        // bootstrap.toml), then this needs to explicitly update the dylib search
1714        // path.
1715        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
1716        let doc_generator_guard =
1717            builder.msg(Kind::Run, "lint-docs", None, self.build_compiler, self.target);
1718        cmd.run(builder);
1719        drop(doc_generator_guard);
1720
1721        // Run rustbook/mdbook to generate the HTML pages.
1722        builder.ensure(RustbookSrc {
1723            target: self.target,
1724            name: "rustc".to_owned(),
1725            src: out_base,
1726            parent: Some(self),
1727            languages: vec![],
1728            build_compiler: None,
1729        });
1730    }
1731}
1732
1733/// Documents the reference.
1734/// It has to always be done using a stage 1+ compiler, because it references in-tree
1735/// compiler/stdlib concepts.
1736#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1737pub struct Reference {
1738    build_compiler: Compiler,
1739    target: TargetSelection,
1740}
1741
1742impl CommandLineStep for Reference {
1743    type Output = ();
1744
1745    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1746        run.path("src/doc/reference")
1747    }
1748
1749    fn is_default_step(builder: &Builder<'_>) -> bool {
1750        builder.config.docs
1751    }
1752
1753    fn make_run(run: RunConfig<'_>) {
1754        // Bump the stage to 2, because the reference requires an in-tree compiler.
1755        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1756        // in stage 1.
1757        // FIXME: create a shared method on builder for auto-bumping, and print some warning when
1758        // it happens.
1759        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1760            run.builder.top_stage
1761        } else {
1762            2
1763        };
1764
1765        run.builder.ensure(Reference {
1766            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1767            target: run.target,
1768        });
1769    }
1770
1771    /// Builds the reference book.
1772    fn run(self, builder: &Builder<'_>) {
1773        builder.require_submodule("src/doc/reference", None);
1774
1775        // This is needed for generating links to the standard library using
1776        // the mdbook-spec plugin.
1777        builder.std(self.build_compiler, builder.config.host_target);
1778
1779        // Run rustbook/mdbook to generate the HTML pages.
1780        builder.ensure(RustbookSrc {
1781            target: self.target,
1782            name: "reference".to_owned(),
1783            src: builder.src.join("src/doc/reference"),
1784            build_compiler: Some(self.build_compiler),
1785            parent: Some(self),
1786            languages: vec![],
1787        });
1788    }
1789}