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