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!["--markdown-css", "rust.css", "--markdown-no-toc", "--index-page", &index_page]
714            }
715            DocumentationFormat::Json => vec![],
716        };
717
718        if !builder.config.docs_minification {
719            extra_args.push("--disable-minification");
720        }
721        // For `--index-page` and `--output-format=json`.
722        extra_args.push("-Zunstable-options");
723
724        let target_doc_dir_name =
725            if self.format == DocumentationFormat::Json { "json-doc" } else { "doc" };
726        let target_dir = builder
727            .stage_out(self.build_compiler, Mode::Std)
728            .join(target)
729            .join(target_doc_dir_name);
730
731        // This is directory where the compiler will place the output of the command.
732        // We will then copy the files from this directory into the final `out` directory, the specified
733        // as a function parameter.
734        let out_dir = target_dir.join(target).join("doc");
735
736        let mut cargo = doc_std(
737            builder,
738            self.format,
739            self.build_compiler,
740            target,
741            &target_dir,
742            &extra_args,
743            &crates,
744        );
745        match self.format {
746            DocumentationFormat::Html => {}
747            DocumentationFormat::Json => {
748                // We have to pass these directly to cargo, rather than through RUSTDOCFLAGS,
749                // otherwise Cargo will not detect freshness of the output correctly, and keep
750                // rebuilding the docs on every invocation.
751                cargo.args(["-Zunstable-options", "--output-format", "json"]);
752            }
753        }
754
755        let description =
756            format!("library{} in {} format", crate_description(&crates), self.format.as_str());
757
758        {
759            let _guard =
760                builder.msg(Kind::Doc, description, Mode::Std, self.build_compiler, target);
761
762            cargo.into_cmd().run(builder);
763            builder.cp_link_r(&out_dir, &out);
764        }
765
766        // Open if the format is HTML
767        if let DocumentationFormat::Html = self.format {
768            if builder.paths.iter().any(|path| path.ends_with("library")) {
769                // For `x.py doc library --open`, open `std` by default.
770                let index = out.join("std").join("index.html");
771                builder.maybe_open_in_browser::<Self>(index);
772            } else {
773                for requested_crate in crates {
774                    if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) {
775                        let index = out.join(requested_crate).join("index.html");
776                        builder.maybe_open_in_browser::<Self>(index);
777                        break;
778                    }
779                }
780            }
781        }
782
783        out
784    }
785
786    fn metadata(&self) -> Option<StepMetadata> {
787        Some(
788            StepMetadata::doc("std", self.target)
789                .built_by(self.build_compiler)
790                .with_metadata(format!("crates=[{}]", self.crates.join(","))),
791        )
792    }
793}
794
795/// Name of the crates that are visible to consumers of the standard library.
796/// Documentation for internal crates is handled by the rustc step, so internal crates will show
797/// up there.
798///
799/// Order here is important!
800/// Crates need to be processed starting from the leaves, otherwise rustdoc will not
801/// create correct links between crates because rustdoc depends on the
802/// existence of the output directories to know if it should be a local
803/// or remote link.
804const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"];
805
806#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
807pub enum DocumentationFormat {
808    Html,
809    Json,
810}
811
812impl DocumentationFormat {
813    fn as_str(&self) -> &str {
814        match self {
815            DocumentationFormat::Html => "HTML",
816            DocumentationFormat::Json => "JSON",
817        }
818    }
819}
820
821/// Prepare a Cargo command for building the documentation for public standard library crates.
822fn doc_std(
823    builder: &Builder<'_>,
824    format: DocumentationFormat,
825    build_compiler: Compiler,
826    target: TargetSelection,
827    target_dir: &Path,
828    extra_args: &[&str],
829    requested_crates: &[String],
830) -> builder::Cargo {
831    let mut cargo = builder::Cargo::new(
832        builder,
833        build_compiler,
834        Mode::Std,
835        SourceType::InTree,
836        target,
837        Kind::Doc,
838    );
839
840    compile::std_cargo(builder, target, &mut cargo, requested_crates);
841    cargo
842        .arg("--no-deps")
843        .arg("--target-dir")
844        .arg(&*target_dir.to_string_lossy())
845        .arg("-Zrustdoc-map")
846        .rustdocflag("--extern-html-root-url")
847        .rustdocflag("std_detect=https://docs.rs/std_detect/latest/")
848        .rustdocflag("--extern-html-root-takes-precedence")
849        .rustdocflag("--resource-suffix")
850        .rustdocflag(&builder.version);
851    for arg in extra_args {
852        cargo.rustdocflag(arg);
853    }
854
855    // This is needed for cargo-semver-checks and potentially other downstream tools that consume
856    // the JSON data.
857    if format == DocumentationFormat::Json || builder.config.library_docs_private_items {
858        cargo.rustdocflag("--document-private-items").rustdocflag("--document-hidden-items");
859    }
860    cargo
861}
862
863/// Prepare a compiler that will be able to document something for `target` at `stage`.
864pub fn prepare_doc_compiler(
865    builder: &Builder<'_>,
866    target: TargetSelection,
867    stage: u32,
868) -> Compiler {
869    assert!(stage > 0, "Cannot document anything in stage 0");
870    let build_compiler = builder.compiler(stage - 1, builder.host_target);
871    builder.std(build_compiler, target);
872    build_compiler
873}
874
875/// Run rustdoc to merge cross-crate info metadata (like the search index) from individual
876/// executions of rustdoc into `out_dir`.
877/// The `json_files` parameter should contain paths to JSON file artifacts generated by previous
878/// executions of `cargo doc`.
879fn merge_rustdoc_cci(
880    builder: &Builder<'_>,
881    build_compiler: Compiler,
882    json_files: &[PathBuf],
883    out_dir: &Path,
884) {
885    let mut cmd = builder.rustdoc_cmd(build_compiler);
886
887    cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(out_dir);
888
889    if !builder.config.docs_minification {
890        cmd.arg("--disable-minification");
891    }
892
893    for json_file in json_files {
894        cmd.arg("--read-doc-meta-dir").arg(json_file.parent().unwrap());
895    }
896
897    cmd.run(builder);
898}
899
900/// Generate the combined compiler + tools docs for a given toolchain.
901/// This contains both the compiler docs, docs of rustc_private tools (miri, clippy, etc.), cargo
902/// and also some bootstrap related tools (bootstrap itself, compiletest, tidy, etc.).
903///
904/// It gets hosted at https://doc.rust-lang.org/nightly/nightly-rustc/index.html.
905///
906/// Compiler documentation is distributed separately, so we make sure
907/// we do not merge it with the other documentation from std, test and
908/// proc_macros. This is largely just a wrapper around `cargo doc`.
909///
910/// Returns a path to a directory with the generated documentation.
911#[derive(Debug, Clone, Hash, PartialEq, Eq)]
912pub struct CompilerWithTools {
913    build_compiler: Compiler,
914    target: TargetSelection,
915    stage: u32,
916}
917
918impl CompilerWithTools {
919    /// Document `stage` compiler for the given `target`.
920    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
921        let build_compiler = prepare_doc_compiler(builder, target, stage);
922        Self { build_compiler, target, stage }
923    }
924}
925
926impl CommandLineStep for CompilerWithTools {
927    type Output = PathBuf;
928    const IS_HOST: bool = true;
929
930    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
931        run.alias("compiler-with-tools")
932    }
933
934    fn is_default_step(builder: &Builder<'_>) -> bool {
935        builder.config.compiler_docs
936    }
937
938    fn make_run(run: RunConfig<'_>) {
939        run.builder.ensure(CompilerWithTools::for_stage(
940            run.builder,
941            run.builder.top_stage,
942            run.target,
943        ));
944    }
945
946    fn run(self, builder: &Builder<'_>) -> Self::Output {
947        let CompilerWithTools { target, build_compiler, stage } = self;
948
949        // This is the intended out directory for combined compiler documentation.
950        let out = builder.compiler_doc_out(target);
951        let _ = fs::remove_dir_all(&out);
952
953        let _guard =
954            builder.msg(Kind::Doc, "compiler-with-tools", Mode::Rustc, build_compiler, target);
955
956        let combined_docs = vec![
957            builder.ensure(Rustc::for_stage(builder, stage, target)),
958            builder.ensure(Rustdoc::new(builder, target)),
959            builder.ensure(Rustfmt::new(builder, target)),
960            builder.ensure(Clippy::new(builder, target)),
961            builder.ensure(Miri::new(builder, target)),
962            builder.ensure(Cargo::new(builder, target)),
963            builder.ensure(Tidy::new(builder, target)),
964            builder.ensure(Bootstrap::new(builder, target)),
965            builder.ensure(BuildHelper::new(builder, target)),
966            builder.ensure(Compiletest::new(builder, target)),
967            builder.ensure(RunMakeSupport::new(builder, target)),
968        ];
969
970        if !builder.config.dry_run() {
971            // Now copy all the individual docs into a single directory
972            let mut json_files = vec![];
973            for docs in combined_docs {
974                json_files.extend(docs.artifacts.json_files);
975
976                // Doc directories to link to the shared output directory
977                // We add the host doc dirs, which should already be symlinked in the target
978                // docs dir at this point (see `merge_host_and_target_docs`).
979                let dirs_to_copy: Vec<_> = docs
980                    .artifacts
981                    .target_dirs
982                    .iter()
983                    .chain(docs.artifacts.host_dirs.iter())
984                    .map(|d| d.file_name().unwrap().to_str().unwrap())
985                    .collect();
986                for dir in dirs_to_copy {
987                    // Link the docs dir
988                    let docs_dir = docs.out_dir.join(dir);
989                    assert!(docs_dir.exists(), "Docs directory {docs_dir:?} does not exist.");
990                    let out_docs_dir = out.join(dir);
991                    builder.create_dir(&out_docs_dir);
992                    builder.cp_link_r(&docs_dir, &out_docs_dir);
993
994                    // And the src dir
995                    let src_dir = docs.out_dir.join("src").join(dir);
996                    assert!(src_dir.exists(), "Docs source directory {src_dir:?} does not exist.");
997                    let out_src_dir = out.join("src").join(dir);
998                    builder.create_dir(&out_src_dir);
999                    builder.cp_link_r(&src_dir, &out_src_dir);
1000                }
1001            }
1002            // And finally merge all the CCI metadata
1003            merge_rustdoc_cci(builder, build_compiler, &json_files, &out);
1004        }
1005
1006        // Handle `--open`.
1007        builder.open_in_browser(out.join("index.html"));
1008        out
1009    }
1010
1011    fn metadata(&self) -> Option<StepMetadata> {
1012        Some(StepMetadata::doc("CompilerWithTools", self.target).built_by(self.build_compiler))
1013    }
1014}
1015
1016/// Output of a Doc step.
1017#[derive(Clone)]
1018pub struct BuiltDocs {
1019    /// Target doc directory with the generated documentation.
1020    out_dir: PathBuf,
1021    /// Doc artifacts gathered from Cargo during the doc build.
1022    artifacts: DocArtifacts,
1023}
1024
1025/// Document the compiler for the given `target` using rustdoc from `build_compiler`.
1026///
1027/// Return the path to the generated rustc documentation directory.
1028#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1029pub struct Rustc {
1030    build_compiler: Compiler,
1031    target: TargetSelection,
1032    crates: Vec<String>,
1033}
1034
1035impl Rustc {
1036    /// Document `stage` compiler for the given `target`.
1037    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {
1038        let build_compiler = prepare_doc_compiler(builder, target, stage);
1039        Self::from_build_compiler(builder, build_compiler, target)
1040    }
1041
1042    fn from_build_compiler(
1043        builder: &Builder<'_>,
1044        build_compiler: Compiler,
1045        target: TargetSelection,
1046    ) -> Self {
1047        let crates = builder
1048            .in_tree_crates("rustc-main", Some(target))
1049            .into_iter()
1050            .map(|krate| krate.name.to_string())
1051            .collect();
1052        Self { build_compiler, target, crates }
1053    }
1054}
1055
1056impl CommandLineStep for Rustc {
1057    type Output = BuiltDocs;
1058    const IS_HOST: bool = true;
1059
1060    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1061        run.crate_or_deps("rustc-main").path("compiler")
1062    }
1063
1064    fn is_default_step(builder: &Builder<'_>) -> bool {
1065        builder.config.compiler_docs
1066    }
1067
1068    fn make_run(run: RunConfig<'_>) {
1069        run.builder.ensure(Rustc::for_stage(run.builder, run.builder.top_stage, run.target));
1070    }
1071
1072    /// Generates compiler documentation.
1073    ///
1074    /// This will generate all documentation for compiler and dependencies.
1075    /// Compiler documentation is distributed separately, so we make sure
1076    /// we do not merge it with the other documentation from std, test and
1077    /// proc_macros. This is largely just a wrapper around `cargo doc`.
1078    fn run(self, builder: &Builder<'_>) -> Self::Output {
1079        let target = self.target;
1080
1081        // Build the standard library, so that proc-macros can use it.
1082        // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.)
1083        let build_compiler = self.build_compiler;
1084        builder.std(build_compiler, builder.config.host_target);
1085
1086        let _guard = builder.msg(
1087            Kind::Doc,
1088            format!("compiler{}", crate_description(&self.crates)),
1089            Mode::Rustc,
1090            build_compiler,
1091            target,
1092        );
1093
1094        // Build cargo command.
1095        let mut cargo = builder::Cargo::new(
1096            builder,
1097            build_compiler,
1098            Mode::Rustc,
1099            SourceType::InTree,
1100            target,
1101            Kind::Doc,
1102        );
1103
1104        cargo.rustdocflag("--document-private-items");
1105        // Since we always pass --document-private-items, there's no need to warn about linking to private items.
1106        cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
1107        cargo.rustdocflag("--enable-index-page");
1108        cargo.rustdocflag("-Znormalize-docs");
1109        cargo.rustdocflag("--show-type-layout");
1110        // FIXME: `--generate-link-to-definition` tries to resolve cfged out code
1111        // see https://github.com/rust-lang/rust/pull/122066#issuecomment-1983049222
1112        // If there is any bug, please comment out the next line.
1113        cargo.rustdocflag("--generate-link-to-definition");
1114        cargo.rustdocflag("--generate-macro-expansion");
1115
1116        compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
1117
1118        // Only include compiler crates, no dependencies of those, such as `libc`.
1119        // Do link to dependencies on `docs.rs` however using `rustdoc-map`.
1120        cargo.arg("--no-deps");
1121        cargo.arg("-Zrustdoc-map");
1122
1123        // FIXME: `-Zrustdoc-map` does not yet correctly work for transitive dependencies,
1124        // once this is no longer an issue the special case for `ena` can be removed.
1125        cargo.rustdocflag("--extern-html-root-url");
1126        cargo.rustdocflag("ena=https://docs.rs/ena/latest/");
1127
1128        let cargo_target_dir = builder.stage_out(build_compiler, Mode::Rustc);
1129        let target_doc_dir = cargo_target_dir.join(target).join("doc");
1130        let host_doc_dir = cargo_target_dir.join("doc");
1131        for krate in &*self.crates {
1132            // Create all crate output directories first to make sure rustdoc uses
1133            // relative links.
1134            // FIXME: Cargo should probably do this itself.
1135            let dir_name = normalize_doc_crate_name(krate);
1136            t!(fs::create_dir_all(target_doc_dir.join(&*dir_name)));
1137            cargo.arg("-p").arg(krate);
1138        }
1139
1140        let artifacts = create_docs_and_gather_artifacts(builder, cargo);
1141        artifacts.sanity_check_crates(builder, self.crates.iter());
1142
1143        if !builder.config.dry_run() {
1144            merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir);
1145            merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir);
1146        }
1147
1148        // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md`
1149        // with no particular explicit doc requested (e.g. library/core).
1150        if builder.was_invoked_explicitly::<Self>(Kind::Doc) {
1151            let index = if builder.paths.iter().any(|path| path.ends_with("compiler")) {
1152                // For `x.py doc compiler --open`, open `rustc_middle` by default.
1153                target_doc_dir.join("rustc_middle").join("index.html")
1154            } else if let Some(krate) = self.crates.first() {
1155                // Let's open the first crate documentation page:
1156                target_doc_dir.join(normalize_doc_crate_name(krate)).join("index.html")
1157            } else {
1158                target_doc_dir.clone()
1159            };
1160            builder.open_in_browser(index);
1161        }
1162
1163        BuiltDocs { out_dir: target_doc_dir, artifacts }
1164    }
1165
1166    fn metadata(&self) -> Option<StepMetadata> {
1167        Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler))
1168    }
1169}
1170
1171/// Stores generated documentation artifacts.
1172#[derive(Clone, Debug)]
1173struct DocArtifacts {
1174    /// Directories with HTML docs for host (proc-macro) crates.
1175    host_dirs: Vec<PathBuf>,
1176    /// Directories with HTML docs for target crates.
1177    target_dirs: Vec<PathBuf>,
1178    /// JSON files used to create the final CCI index
1179    json_files: Vec<PathBuf>,
1180}
1181
1182impl DocArtifacts {
1183    /// Ensure that all passed crates were documented.
1184    fn sanity_check_crates<S>(&self, builder: &Builder<'_>, crates: impl Iterator<Item = S>)
1185    where
1186        S: AsRef<str>,
1187    {
1188        if builder.config.dry_run() {
1189            return;
1190        }
1191        let crate_names: HashSet<&str> = self
1192            .host_dirs
1193            .iter()
1194            .chain(self.target_dirs.iter())
1195            .filter_map(|d| d.file_name().and_then(|d| d.to_str()))
1196            .collect();
1197        for krate in crates {
1198            let krate = krate.as_ref();
1199            let krate = normalize_doc_crate_name(krate);
1200            if !crate_names.contains(krate.as_str()) {
1201                eprintln!("ERROR: crate {krate} was not documented!");
1202                exit_process(1);
1203            }
1204        }
1205    }
1206}
1207
1208/// Run `cargo doc` and gather generated documentation artifacts.
1209fn create_docs_and_gather_artifacts(builder: &Builder<'_>, cargo: builder::Cargo) -> DocArtifacts {
1210    let mut json_files = vec![];
1211    let mut host_dirs = vec![];
1212    let mut target_dirs = vec![];
1213    stream_cargo(builder, cargo, vec![], &mut |msg| {
1214        let CargoMessage::CompilerArtifact { filenames, target } = msg else {
1215            return;
1216        };
1217        if !target.doc {
1218            return;
1219        }
1220        // Note: An alternative way to check host docs would be to check whether the generated
1221        // output is a child of the host doc directory (which we would have to pass to this
1222        // function).
1223        let is_host = target.crate_types.iter().any(|t| t == "proc-macro");
1224        for filename in filenames {
1225            let path = Path::new(filename.as_ref());
1226            let Some(extension) = path.extension().and_then(|ext| ext.to_str()) else {
1227                continue;
1228            };
1229            let path = path.to_path_buf();
1230            if extension == "json" {
1231                json_files.push(path.to_path_buf());
1232            } else if extension == "html" {
1233                if is_host {
1234                    // doc/<crate>/index.html -> doc/<crate>
1235                    host_dirs.push(path.parent().unwrap().to_path_buf());
1236                } else {
1237                    target_dirs.push(path.parent().unwrap().to_path_buf());
1238                }
1239            }
1240        }
1241    });
1242    DocArtifacts { host_dirs, target_dirs, json_files }
1243}
1244
1245/// Merge host and target documentation for a set of crates.
1246/// We pass `--target` when documenting, so Cargo will put the built documentation into two places:
1247/// - `target/doc` - contains documentation of host code, so proc macros
1248/// - `target/<target>/doc` - contains documentation of "normal" code
1249///
1250/// See https://github.com/rust-lang/cargo/issues/7677.
1251///
1252/// To produce a single unified documentation, we want to merge them together.
1253/// We do that by creating symlinks into the target doc dir that will point to the host doc
1254/// directories.
1255/// The target doc directory will then contain the combined docs.
1256fn merge_host_and_target_docs(
1257    builder: &Builder<'_>,
1258    docs: &DocArtifacts,
1259    host_doc_dir: &Path,
1260    target_doc_dir: &Path,
1261) {
1262    // Sanity check that there is no host/target overlap
1263    for dir in &docs.host_dirs {
1264        let name = dir.file_name().and_then(|d| d.to_str()).unwrap();
1265        if let Some(target_dir) = docs.target_dirs.iter().find_map(|d| {
1266            let dirname = d.file_name().and_then(|d| d.to_str())?;
1267            if dirname == name { Some(d) } else { None }
1268        }) {
1269            eprintln!(
1270                "ERROR: host docs directory `{name}` ({dir:?}) is also contained in target doc directory ({target_dir:?})"
1271            );
1272            exit_process(1);
1273        }
1274    }
1275
1276    let target_src_dir = target_doc_dir.join("src");
1277    let host_src_dir = host_doc_dir.join("src");
1278
1279    // Ideally, we would remove all previous symlinks here.
1280    // However, some of the tools actually share the same build docs directory, so we shouldn't do
1281    // that, otherwise they will invalidate one another.
1282
1283    for host_docs_crate in &docs.host_dirs {
1284        let dir_name = host_docs_crate.file_name().unwrap().to_str().unwrap();
1285        // Normalize crate name
1286        let dir_name = normalize_doc_crate_name(dir_name);
1287
1288        t!(symlink_dir(&builder.config, host_docs_crate, &target_doc_dir.join(&dir_name)));
1289
1290        // Also symlink its source directory
1291        let target_src_out = target_src_dir.join(&dir_name);
1292        let host_src_out = host_src_dir.join(&dir_name);
1293        t!(symlink_dir(&builder.config, &host_src_out, &target_src_out));
1294    }
1295
1296    // Sanity check that all directories contain some documentation
1297    for dir in docs.target_dirs.iter().chain(docs.host_dirs.iter()) {
1298        // Making sure the directory exists and is not empty.
1299        assert!(dir.exists(), "Doc directory {dir:?} does not exist");
1300        assert!(t!(dir.read_dir()).next().is_some(), "Doc directory {dir:?} is empty");
1301    }
1302}
1303
1304/// Normalizes crate name to get a name that is used to generate documentation on disk.
1305/// Turns `rustc-main` into `rustc_main`.
1306fn normalize_doc_crate_name(name: &str) -> String {
1307    name.replace("-", "_")
1308}
1309
1310macro_rules! tool_doc {
1311    (
1312        $tool: ident,
1313        $path: literal,
1314        mode = $mode:expr
1315        $(, is_library = $is_library:expr )?
1316        $(, crates = $crates:expr )?
1317        // Subset of nightly features that are allowed to be used when documenting
1318        $(, allow_features: $allow_features:expr )?
1319       ) => {
1320        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
1321        pub struct $tool {
1322            build_compiler: Compiler,
1323            mode: Mode,
1324            target: TargetSelection,
1325        }
1326
1327        impl $tool {
1328            fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool {
1329                let build_compiler = match $mode {
1330                    Mode::ToolRustcPrivate => {
1331                        // Rustdoc needs the rustc sysroot available to build.
1332                        let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target);
1333
1334                        // Build rustc docs so that we generate relative links.
1335                        builder.ensure(Rustc::from_build_compiler(builder, compilers.build_compiler(), target));
1336                        compilers.build_compiler()
1337                    }
1338                    Mode::ToolTarget => {
1339                        // when shipping multiple docs together in one folder,
1340                        // they all need to use the same rustdoc version
1341                        prepare_doc_compiler(builder, builder.host_target, builder.top_stage)
1342                    }
1343                    _ => {
1344                        panic!("Unexpected tool mode for documenting: {:?}", $mode);
1345                    }
1346                };
1347                $tool { build_compiler, mode: $mode, target }
1348            }
1349            fn crates() -> &'static [&'static str] {
1350                &$($crates)?[..]
1351            }
1352        }
1353
1354        impl CommandLineStep for $tool {
1355            type Output = BuiltDocs;
1356            const IS_HOST: bool = true;
1357
1358            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1359                run.path($path)
1360            }
1361
1362            fn is_default_step(builder: &Builder<'_>) -> bool {
1363                builder.config.compiler_docs
1364            }
1365
1366            fn make_run(run: RunConfig<'_>) {
1367                run.builder.ensure($tool::new(run.builder, run.target));
1368            }
1369
1370            /// Generates documentation for a tool.
1371            ///
1372            /// This is largely just a wrapper around `cargo doc`.
1373            fn run(self, builder: &Builder<'_>) -> Self::Output {
1374                let mut source_type = SourceType::InTree;
1375
1376                if let Some(submodule_path) = submodule_path_of(&builder, $path) {
1377                    source_type = SourceType::Submodule;
1378                    builder.require_submodule(&submodule_path, None);
1379                }
1380
1381                let $tool { build_compiler, mode, target } = self;
1382
1383                // Build cargo command.
1384                let mut cargo = prepare_tool_cargo(
1385                    builder,
1386                    build_compiler,
1387                    mode,
1388                    target,
1389                    Kind::Doc,
1390                    $path,
1391                    source_type,
1392                    &[],
1393                );
1394                let allow_features = {
1395                    let mut _value = "";
1396                    $( _value = $allow_features; )?
1397                    _value
1398                };
1399
1400                if !allow_features.is_empty() {
1401                    cargo.allow_features(allow_features);
1402                }
1403
1404                // Only include compiler crates, no dependencies of those, such as `libc`.
1405                cargo.arg("--no-deps");
1406
1407                if false $(|| $is_library)? {
1408                    cargo.arg("--lib");
1409                }
1410
1411                for krate in $tool::crates() {
1412                    cargo.arg("-p").arg(krate);
1413                }
1414
1415                cargo.rustdocflag("--document-private-items");
1416                // Since we always pass --document-private-items, there's no need to warn about linking to private items.
1417                cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
1418                cargo.rustdocflag("--enable-index-page");
1419                cargo.rustdocflag("--show-type-layout");
1420                cargo.rustdocflag("--generate-link-to-definition");
1421
1422                let cargo_target_dir = builder.stage_out(build_compiler, mode);
1423                let target_doc_dir = cargo_target_dir.join(target).join("doc");
1424                let host_doc_dir = cargo_target_dir.join("doc");
1425                for krate in $tool::crates() {
1426                    let dir_name = normalize_doc_crate_name(krate);
1427                    t!(fs::create_dir_all(target_doc_dir.join(&*dir_name)));
1428                }
1429
1430                let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target);
1431                let artifacts = create_docs_and_gather_artifacts(builder, cargo);
1432                artifacts.sanity_check_crates(builder, $tool::crates().iter());
1433
1434                if !builder.config.dry_run() {
1435                    merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir);
1436                    merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir);
1437                }
1438                BuiltDocs { out_dir: target_doc_dir, artifacts }
1439            }
1440
1441            fn metadata(&self) -> Option<StepMetadata> {
1442                Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler))
1443            }
1444        }
1445    }
1446}
1447
1448// NOTE: make sure to register these in `Builder::get_step_description`.
1449tool_doc!(
1450    BuildHelper,
1451    "src/build_helper",
1452    // ideally, this would use ToolBootstrap,
1453    // but we distribute these docs together in the same folder
1454    // as a bunch of stage1 tools, and you can't mix rustdoc versions
1455    // because that breaks cross-crate data (particularly search)
1456    mode = Mode::ToolTarget,
1457    is_library = true,
1458    crates = ["build_helper"]
1459);
1460tool_doc!(
1461    Rustdoc,
1462    "src/tools/rustdoc",
1463    mode = Mode::ToolRustcPrivate,
1464    crates = ["rustdoc", "rustdoc-json-types"]
1465);
1466tool_doc!(
1467    Rustfmt,
1468    "src/tools/rustfmt",
1469    mode = Mode::ToolRustcPrivate,
1470    crates = ["rustfmt-nightly", "rustfmt-config_proc_macro"]
1471);
1472tool_doc!(
1473    Clippy,
1474    "src/tools/clippy",
1475    mode = Mode::ToolRustcPrivate,
1476    crates = ["clippy_config", "clippy_utils"]
1477);
1478tool_doc!(Miri, "src/tools/miri", mode = Mode::ToolRustcPrivate, crates = ["miri"]);
1479tool_doc!(
1480    Cargo,
1481    "src/tools/cargo",
1482    mode = Mode::ToolTarget,
1483    crates = [
1484        "cargo",
1485        "cargo-credential",
1486        "cargo-platform",
1487        "cargo-test-macro",
1488        "cargo-test-support",
1489        "cargo-util",
1490        "cargo-util-schemas",
1491        "crates-io",
1492        "mdman",
1493        "rustfix",
1494    ],
1495    // Required because of the im-rc dependency of Cargo, which automatically opts into the
1496    // "specialization" feature in its build script when it detects a nightly toolchain.
1497    allow_features: "specialization"
1498);
1499tool_doc!(Tidy, "src/tools/tidy", mode = Mode::ToolTarget, crates = ["tidy"]);
1500tool_doc!(
1501    Bootstrap,
1502    "src/bootstrap",
1503    mode = Mode::ToolTarget,
1504    is_library = true,
1505    crates = ["bootstrap"]
1506);
1507tool_doc!(
1508    RunMakeSupport,
1509    "src/tools/run-make-support",
1510    mode = Mode::ToolTarget,
1511    is_library = true,
1512    crates = ["run_make_support"]
1513);
1514tool_doc!(
1515    Compiletest,
1516    "src/tools/compiletest",
1517    mode = Mode::ToolTarget,
1518    is_library = true,
1519    crates = ["compiletest"]
1520);
1521
1522#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1523pub struct ErrorIndex {
1524    compilers: RustcPrivateCompilers,
1525}
1526
1527impl CommandLineStep for ErrorIndex {
1528    type Output = ();
1529    const IS_HOST: bool = true;
1530
1531    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1532        run.path("src/tools/error_index_generator")
1533    }
1534
1535    fn is_default_step(builder: &Builder<'_>) -> bool {
1536        builder.config.docs
1537    }
1538
1539    fn make_run(run: RunConfig<'_>) {
1540        run.builder.ensure(ErrorIndex {
1541            compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target),
1542        });
1543    }
1544
1545    /// Generates the HTML rendered error-index by running the
1546    /// `error_index_generator` tool.
1547    fn run(self, builder: &Builder<'_>) {
1548        builder.info(&format!("Documenting error index ({})", self.compilers.target()));
1549        let out = builder.doc_out(self.compilers.target());
1550        t!(fs::create_dir_all(&out));
1551        tool::ErrorIndex::command(builder, self.compilers)
1552            .arg("html")
1553            .arg(&out)
1554            .arg(&builder.version)
1555            .run(builder);
1556
1557        let index = out.join("error-index.html");
1558        builder.maybe_open_in_browser::<Self>(index);
1559    }
1560
1561    fn metadata(&self) -> Option<StepMetadata> {
1562        Some(
1563            StepMetadata::doc("error-index", self.compilers.target())
1564                .built_by(self.compilers.build_compiler()),
1565        )
1566    }
1567}
1568
1569/// Runs the `unstable-book-gen` tool and returns a path to the generate unstable book markdown
1570/// files.
1571#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1572pub struct UnstableBookGen {
1573    build_compiler: Compiler,
1574    target: TargetSelection,
1575}
1576
1577impl CommandLineStep for UnstableBookGen {
1578    type Output = PathBuf;
1579    const IS_HOST: bool = true;
1580
1581    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1582        run.path("src/tools/unstable-book-gen")
1583    }
1584
1585    fn is_default_step(builder: &Builder<'_>) -> bool {
1586        builder.config.docs
1587    }
1588
1589    fn make_run(run: RunConfig<'_>) {
1590        run.builder.ensure(UnstableBookGen {
1591            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),
1592            target: run.target,
1593        });
1594    }
1595
1596    fn run(self, builder: &Builder<'_>) -> Self::Output {
1597        let target = self.target;
1598        let rustc_path = builder.rustc(self.build_compiler);
1599
1600        builder.info(&format!("Generating unstable book md files ({target})"));
1601        let out = builder.out.join(target).join("md-doc").join("unstable-book");
1602        builder.create_dir(&out);
1603        builder.remove_dir(&out);
1604        let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
1605        cmd.arg(builder.src.join("library"));
1606        cmd.arg(builder.src.join("compiler"));
1607        cmd.arg(builder.src.join("src"));
1608        cmd.arg(rustc_path);
1609        cmd.arg(&out);
1610
1611        // Running rustc requires the library path if rust.rpath = false
1612        // or any other libraries are in a custom location.
1613        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
1614
1615        cmd.run(builder);
1616        out
1617    }
1618}
1619
1620/// Builds the Rust compiler book.
1621#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1622pub struct RustcBook {
1623    build_compiler: Compiler,
1624    target: TargetSelection,
1625    /// Test that the examples of lints in the book produce the correct lints in the expected
1626    /// format.
1627    validate: bool,
1628}
1629
1630impl RustcBook {
1631    pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self {
1632        Self { build_compiler, target, validate: true }
1633    }
1634}
1635
1636impl CommandLineStep for RustcBook {
1637    type Output = ();
1638    const IS_HOST: bool = true;
1639
1640    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1641        run.path("src/doc/rustc")
1642    }
1643
1644    fn is_default_step(builder: &Builder<'_>) -> bool {
1645        builder.config.docs
1646    }
1647
1648    fn make_run(run: RunConfig<'_>) {
1649        // Bump the stage to 2, because the rustc book requires an in-tree compiler.
1650        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1651        // in stage 1.
1652        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1653            run.builder.top_stage
1654        } else {
1655            2
1656        };
1657
1658        run.builder.ensure(RustcBook {
1659            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1660            target: run.target,
1661            validate: false,
1662        });
1663    }
1664
1665    /// Builds the rustc book.
1666    ///
1667    /// The lints are auto-generated by a tool, and then merged into the book
1668    /// in the "md-doc" directory in the build output directory. Then
1669    /// "rustbook" is used to convert it to HTML.
1670    fn run(self, builder: &Builder<'_>) {
1671        let out_base = builder.out.join(self.target).join("md-doc").join("rustc");
1672        t!(fs::create_dir_all(&out_base));
1673        let out_listing = out_base.join("src/lints");
1674        builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base);
1675        builder.info(&format!("Generating lint docs ({})", self.target));
1676
1677        let rustc = builder.rustc(self.build_compiler);
1678        // The tool runs `rustc` for extracting output examples, so it needs a
1679        // functional sysroot.
1680        builder.std(self.build_compiler, self.target);
1681        let mut cmd = builder.tool_cmd(Tool::LintDocs);
1682        cmd.arg("--build-rustc-stage");
1683        cmd.arg(self.build_compiler.stage.to_string());
1684        cmd.arg("--src");
1685        cmd.arg(builder.src.join("compiler"));
1686        cmd.arg("--out");
1687        cmd.arg(&out_listing);
1688        cmd.arg("--rustc");
1689        cmd.arg(&rustc);
1690        cmd.arg("--rustc-target").arg(self.target.rustc_target_arg());
1691        if let Some(target_linker) = builder.linker(self.target) {
1692            cmd.arg("--rustc-linker").arg(target_linker);
1693        }
1694        if builder.is_verbose() {
1695            cmd.arg("--verbose");
1696        }
1697        if self.validate {
1698            cmd.arg("--validate");
1699        }
1700        // We need to validate nightly features, even on the stable channel.
1701        // Set this unconditionally as the stage0 compiler may be being used to
1702        // document.
1703        cmd.env("RUSTC_BOOTSTRAP", "1");
1704
1705        // If the lib directories are in an unusual location (changed in
1706        // bootstrap.toml), then this needs to explicitly update the dylib search
1707        // path.
1708        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
1709        let doc_generator_guard =
1710            builder.msg(Kind::Run, "lint-docs", None, self.build_compiler, self.target);
1711        cmd.run(builder);
1712        drop(doc_generator_guard);
1713
1714        // Run rustbook/mdbook to generate the HTML pages.
1715        builder.ensure(RustbookSrc {
1716            target: self.target,
1717            name: "rustc".to_owned(),
1718            src: out_base,
1719            parent: Some(self),
1720            languages: vec![],
1721            build_compiler: None,
1722        });
1723    }
1724}
1725
1726/// Documents the reference.
1727/// It has to always be done using a stage 1+ compiler, because it references in-tree
1728/// compiler/stdlib concepts.
1729#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1730pub struct Reference {
1731    build_compiler: Compiler,
1732    target: TargetSelection,
1733}
1734
1735impl CommandLineStep for Reference {
1736    type Output = ();
1737
1738    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1739        run.path("src/doc/reference")
1740    }
1741
1742    fn is_default_step(builder: &Builder<'_>) -> bool {
1743        builder.config.docs
1744    }
1745
1746    fn make_run(run: RunConfig<'_>) {
1747        // Bump the stage to 2, because the reference requires an in-tree compiler.
1748        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1749        // in stage 1.
1750        // FIXME: create a shared method on builder for auto-bumping, and print some warning when
1751        // it happens.
1752        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1753            run.builder.top_stage
1754        } else {
1755            2
1756        };
1757
1758        run.builder.ensure(Reference {
1759            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1760            target: run.target,
1761        });
1762    }
1763
1764    /// Builds the reference book.
1765    fn run(self, builder: &Builder<'_>) {
1766        builder.require_submodule("src/doc/reference", None);
1767
1768        // This is needed for generating links to the standard library using
1769        // the mdbook-spec plugin.
1770        builder.std(self.build_compiler, builder.config.host_target);
1771
1772        // Run rustbook/mdbook to generate the HTML pages.
1773        builder.ensure(RustbookSrc {
1774            target: self.target,
1775            name: "reference".to_owned(),
1776            src: builder.src.join("src/doc/reference"),
1777            build_compiler: Some(self.build_compiler),
1778            parent: Some(self),
1779            languages: vec![],
1780        });
1781    }
1782}