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"];
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
936        compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
937        cargo.arg("-Zskip-rustdoc-fingerprint");
938
939        // Only include compiler crates, no dependencies of those, such as `libc`.
940        // Do link to dependencies on `docs.rs` however using `rustdoc-map`.
941        cargo.arg("--no-deps");
942        cargo.arg("-Zrustdoc-map");
943
944        // FIXME: `-Zrustdoc-map` does not yet correctly work for transitive dependencies,
945        // once this is no longer an issue the special case for `ena` can be removed.
946        cargo.rustdocflag("--extern-html-root-url");
947        cargo.rustdocflag("ena=https://docs.rs/ena/latest/");
948
949        let mut to_open = None;
950
951        let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc");
952        for krate in &*self.crates {
953            // Create all crate output directories first to make sure rustdoc uses
954            // relative links.
955            // FIXME: Cargo should probably do this itself.
956            let dir_name = krate.replace('-', "_");
957            t!(fs::create_dir_all(out_dir.join(&*dir_name)));
958            cargo.arg("-p").arg(krate);
959            if to_open.is_none() {
960                to_open = Some(dir_name);
961            }
962        }
963
964        // This uses a shared directory so that librustdoc documentation gets
965        // correctly built and merged with the rustc documentation.
966        //
967        // This is needed because rustdoc is built in a different directory from
968        // rustc. rustdoc needs to be able to see everything, for example when
969        // merging the search index, or generating local (relative) links.
970        symlink_dir_force(&builder.config, &out, &out_dir);
971        // Cargo puts proc macros in `target/doc` even if you pass `--target`
972        // explicitly (https://github.com/rust-lang/cargo/issues/7677).
973        let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc");
974        symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
975
976        cargo.into_cmd().run(builder);
977
978        if !builder.config.dry_run() {
979            // Sanity check on linked compiler crates
980            for krate in &*self.crates {
981                let dir_name = krate.replace('-', "_");
982                // Making sure the directory exists and is not empty.
983                assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some());
984            }
985        }
986
987        if builder.paths.iter().any(|path| path.ends_with("compiler")) {
988            // For `x.py doc compiler --open`, open `rustc_middle` by default.
989            let index = out.join("rustc_middle").join("index.html");
990            builder.open_in_browser(index);
991        } else if let Some(krate) = to_open {
992            // Let's open the first crate documentation page:
993            let index = out.join(krate).join("index.html");
994            builder.open_in_browser(index);
995        }
996    }
997
998    fn metadata(&self) -> Option<StepMetadata> {
999        Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler))
1000    }
1001}
1002
1003macro_rules! tool_doc {
1004    (
1005        $tool: ident,
1006        $path: literal,
1007        mode = $mode:expr
1008        $(, is_library = $is_library:expr )?
1009        $(, crates = $crates:expr )?
1010        // Subset of nightly features that are allowed to be used when documenting
1011        $(, allow_features: $allow_features:expr )?
1012       ) => {
1013        #[derive(Debug, Clone, Hash, PartialEq, Eq)]
1014        pub struct $tool {
1015            build_compiler: Compiler,
1016            mode: Mode,
1017            target: TargetSelection,
1018        }
1019
1020        impl Step for $tool {
1021            type Output = ();
1022            const IS_HOST: bool = true;
1023
1024            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1025                run.path($path)
1026            }
1027
1028            fn is_default_step(builder: &Builder<'_>) -> bool {
1029                builder.config.compiler_docs
1030            }
1031
1032            fn make_run(run: RunConfig<'_>) {
1033                let target = run.target;
1034                let build_compiler = match $mode {
1035                    Mode::ToolRustcPrivate => {
1036                        // Rustdoc needs the rustc sysroot available to build.
1037                        let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target);
1038
1039                        // Build rustc docs so that we generate relative links.
1040                        run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target));
1041                        compilers.build_compiler()
1042                    }
1043                    Mode::ToolTarget => {
1044                        // when shipping multiple docs together in one folder,
1045                        // they all need to use the same rustdoc version
1046                        prepare_doc_compiler(run.builder, run.builder.host_target, run.builder.top_stage)
1047                    }
1048                    _ => {
1049                        panic!("Unexpected tool mode for documenting: {:?}", $mode);
1050                    }
1051                };
1052
1053                run.builder.ensure($tool { build_compiler, mode: $mode, target });
1054            }
1055
1056            /// Generates documentation for a tool.
1057            ///
1058            /// This is largely just a wrapper around `cargo doc`.
1059            fn run(self, builder: &Builder<'_>) {
1060                let mut source_type = SourceType::InTree;
1061
1062                if let Some(submodule_path) = submodule_path_of(&builder, $path) {
1063                    source_type = SourceType::Submodule;
1064                    builder.require_submodule(&submodule_path, None);
1065                }
1066
1067                let $tool { build_compiler, mode, target } = self;
1068
1069                // This is the intended out directory for compiler documentation.
1070                let out = builder.compiler_doc_out(target);
1071                t!(fs::create_dir_all(&out));
1072
1073                // Build cargo command.
1074                let mut cargo = prepare_tool_cargo(
1075                    builder,
1076                    build_compiler,
1077                    mode,
1078                    target,
1079                    Kind::Doc,
1080                    $path,
1081                    source_type,
1082                    &[],
1083                );
1084                let allow_features = {
1085                    let mut _value = "";
1086                    $( _value = $allow_features; )?
1087                    _value
1088                };
1089
1090                if !allow_features.is_empty() {
1091                    cargo.allow_features(allow_features);
1092                }
1093
1094                cargo.arg("-Zskip-rustdoc-fingerprint");
1095                // Only include compiler crates, no dependencies of those, such as `libc`.
1096                cargo.arg("--no-deps");
1097
1098                if false $(|| $is_library)? {
1099                    cargo.arg("--lib");
1100                }
1101
1102                $(for krate in $crates {
1103                    cargo.arg("-p").arg(krate);
1104                })?
1105
1106                cargo.rustdocflag("--document-private-items");
1107                // Since we always pass --document-private-items, there's no need to warn about linking to private items.
1108                cargo.rustdocflag("-Arustdoc::private-intra-doc-links");
1109                cargo.rustdocflag("--enable-index-page");
1110                cargo.rustdocflag("--show-type-layout");
1111                cargo.rustdocflag("--generate-link-to-definition");
1112
1113                let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc");
1114                $(for krate in $crates {
1115                    let dir_name = krate.replace("-", "_");
1116                    t!(fs::create_dir_all(out_dir.join(&*dir_name)));
1117                })?
1118
1119                // Symlink compiler docs to the output directory of rustdoc documentation.
1120                symlink_dir_force(&builder.config, &out, &out_dir);
1121                let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc");
1122                symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);
1123
1124                let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target);
1125                cargo.into_cmd().run(builder);
1126
1127                if !builder.config.dry_run() {
1128                    // Sanity check on linked doc directories
1129                    $(for krate in $crates {
1130                        let dir_name = krate.replace("-", "_");
1131                        // Making sure the directory exists and is not empty.
1132                        assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some());
1133                    })?
1134                }
1135            }
1136
1137            fn metadata(&self) -> Option<StepMetadata> {
1138                Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler))
1139            }
1140        }
1141    }
1142}
1143
1144// NOTE: make sure to register these in `Builder::get_step_description`.
1145tool_doc!(
1146    BuildHelper,
1147    "src/build_helper",
1148    // ideally, this would use ToolBootstrap,
1149    // but we distribute these docs together in the same folder
1150    // as a bunch of stage1 tools, and you can't mix rustdoc versions
1151    // because that breaks cross-crate data (particularly search)
1152    mode = Mode::ToolTarget,
1153    is_library = true,
1154    crates = ["build_helper"]
1155);
1156tool_doc!(
1157    Rustdoc,
1158    "src/tools/rustdoc",
1159    mode = Mode::ToolRustcPrivate,
1160    crates = ["rustdoc", "rustdoc-json-types"]
1161);
1162tool_doc!(
1163    Rustfmt,
1164    "src/tools/rustfmt",
1165    mode = Mode::ToolRustcPrivate,
1166    crates = ["rustfmt-nightly", "rustfmt-config_proc_macro"]
1167);
1168tool_doc!(
1169    Clippy,
1170    "src/tools/clippy",
1171    mode = Mode::ToolRustcPrivate,
1172    crates = ["clippy_config", "clippy_utils"]
1173);
1174tool_doc!(Miri, "src/tools/miri", mode = Mode::ToolRustcPrivate, crates = ["miri"]);
1175tool_doc!(
1176    Cargo,
1177    "src/tools/cargo",
1178    mode = Mode::ToolTarget,
1179    crates = [
1180        "cargo",
1181        "cargo-credential",
1182        "cargo-platform",
1183        "cargo-test-macro",
1184        "cargo-test-support",
1185        "cargo-util",
1186        "cargo-util-schemas",
1187        "crates-io",
1188        "mdman",
1189        "rustfix",
1190    ],
1191    // Required because of the im-rc dependency of Cargo, which automatically opts into the
1192    // "specialization" feature in its build script when it detects a nightly toolchain.
1193    allow_features: "specialization"
1194);
1195tool_doc!(Tidy, "src/tools/tidy", mode = Mode::ToolTarget, crates = ["tidy"]);
1196tool_doc!(
1197    Bootstrap,
1198    "src/bootstrap",
1199    mode = Mode::ToolTarget,
1200    is_library = true,
1201    crates = ["bootstrap"]
1202);
1203tool_doc!(
1204    RunMakeSupport,
1205    "src/tools/run-make-support",
1206    mode = Mode::ToolTarget,
1207    is_library = true,
1208    crates = ["run_make_support"]
1209);
1210tool_doc!(
1211    Compiletest,
1212    "src/tools/compiletest",
1213    mode = Mode::ToolTarget,
1214    is_library = true,
1215    crates = ["compiletest"]
1216);
1217
1218#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1219pub struct ErrorIndex {
1220    compilers: RustcPrivateCompilers,
1221}
1222
1223impl Step for ErrorIndex {
1224    type Output = ();
1225    const IS_HOST: bool = true;
1226
1227    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1228        run.path("src/tools/error_index_generator")
1229    }
1230
1231    fn is_default_step(builder: &Builder<'_>) -> bool {
1232        builder.config.docs
1233    }
1234
1235    fn make_run(run: RunConfig<'_>) {
1236        run.builder.ensure(ErrorIndex {
1237            compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target),
1238        });
1239    }
1240
1241    /// Generates the HTML rendered error-index by running the
1242    /// `error_index_generator` tool.
1243    fn run(self, builder: &Builder<'_>) {
1244        builder.info(&format!("Documenting error index ({})", self.compilers.target()));
1245        let out = builder.doc_out(self.compilers.target());
1246        t!(fs::create_dir_all(&out));
1247        tool::ErrorIndex::command(builder, self.compilers)
1248            .arg("html")
1249            .arg(&out)
1250            .arg(&builder.version)
1251            .run(builder);
1252
1253        let index = out.join("error-index.html");
1254        builder.maybe_open_in_browser::<Self>(index);
1255    }
1256
1257    fn metadata(&self) -> Option<StepMetadata> {
1258        Some(
1259            StepMetadata::doc("error-index", self.compilers.target())
1260                .built_by(self.compilers.build_compiler()),
1261        )
1262    }
1263}
1264
1265#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1266pub struct UnstableBookGen {
1267    target: TargetSelection,
1268}
1269
1270impl Step for UnstableBookGen {
1271    type Output = ();
1272    const IS_HOST: bool = true;
1273
1274    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1275        run.path("src/tools/unstable-book-gen")
1276    }
1277
1278    fn is_default_step(builder: &Builder<'_>) -> bool {
1279        builder.config.docs
1280    }
1281
1282    fn make_run(run: RunConfig<'_>) {
1283        run.builder.ensure(UnstableBookGen { target: run.target });
1284    }
1285
1286    fn run(self, builder: &Builder<'_>) {
1287        let target = self.target;
1288
1289        builder.info(&format!("Generating unstable book md files ({target})"));
1290        let out = builder.md_doc_out(target).join("unstable-book");
1291        builder.create_dir(&out);
1292        builder.remove_dir(&out);
1293        let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);
1294        cmd.arg(builder.src.join("library"));
1295        cmd.arg(builder.src.join("compiler"));
1296        cmd.arg(builder.src.join("src"));
1297        cmd.arg(out);
1298
1299        cmd.run(builder);
1300    }
1301}
1302
1303fn symlink_dir_force(config: &Config, original: &Path, link: &Path) {
1304    if config.dry_run() {
1305        return;
1306    }
1307    if let Ok(m) = fs::symlink_metadata(link) {
1308        if m.file_type().is_dir() {
1309            t!(fs::remove_dir_all(link));
1310        } else {
1311            // handle directory junctions on windows by falling back to
1312            // `remove_dir`.
1313            t!(fs::remove_file(link).or_else(|_| fs::remove_dir(link)));
1314        }
1315    }
1316
1317    t!(
1318        symlink_dir(config, original, link),
1319        format!("failed to create link from {} -> {}", link.display(), original.display())
1320    );
1321}
1322
1323/// Builds the Rust compiler book.
1324#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1325pub struct RustcBook {
1326    build_compiler: Compiler,
1327    target: TargetSelection,
1328    /// Test that the examples of lints in the book produce the correct lints in the expected
1329    /// format.
1330    validate: bool,
1331}
1332
1333impl RustcBook {
1334    pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self {
1335        Self { build_compiler, target, validate: true }
1336    }
1337}
1338
1339impl Step for RustcBook {
1340    type Output = ();
1341    const IS_HOST: bool = true;
1342
1343    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1344        run.path("src/doc/rustc")
1345    }
1346
1347    fn is_default_step(builder: &Builder<'_>) -> bool {
1348        builder.config.docs
1349    }
1350
1351    fn make_run(run: RunConfig<'_>) {
1352        // Bump the stage to 2, because the rustc book requires an in-tree compiler.
1353        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1354        // in stage 1.
1355        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1356            run.builder.top_stage
1357        } else {
1358            2
1359        };
1360
1361        run.builder.ensure(RustcBook {
1362            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1363            target: run.target,
1364            validate: false,
1365        });
1366    }
1367
1368    /// Builds the rustc book.
1369    ///
1370    /// The lints are auto-generated by a tool, and then merged into the book
1371    /// in the "md-doc" directory in the build output directory. Then
1372    /// "rustbook" is used to convert it to HTML.
1373    fn run(self, builder: &Builder<'_>) {
1374        let out_base = builder.md_doc_out(self.target).join("rustc");
1375        t!(fs::create_dir_all(&out_base));
1376        let out_listing = out_base.join("src/lints");
1377        builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base);
1378        builder.info(&format!("Generating lint docs ({})", self.target));
1379
1380        let rustc = builder.rustc(self.build_compiler);
1381        // The tool runs `rustc` for extracting output examples, so it needs a
1382        // functional sysroot.
1383        builder.std(self.build_compiler, self.target);
1384        let mut cmd = builder.tool_cmd(Tool::LintDocs);
1385        cmd.arg("--build-rustc-stage");
1386        cmd.arg(self.build_compiler.stage.to_string());
1387        cmd.arg("--src");
1388        cmd.arg(builder.src.join("compiler"));
1389        cmd.arg("--out");
1390        cmd.arg(&out_listing);
1391        cmd.arg("--rustc");
1392        cmd.arg(&rustc);
1393        cmd.arg("--rustc-target").arg(self.target.rustc_target_arg());
1394        if let Some(target_linker) = builder.linker(self.target) {
1395            cmd.arg("--rustc-linker").arg(target_linker);
1396        }
1397        if builder.is_verbose() {
1398            cmd.arg("--verbose");
1399        }
1400        if self.validate {
1401            cmd.arg("--validate");
1402        }
1403        // We need to validate nightly features, even on the stable channel.
1404        // Set this unconditionally as the stage0 compiler may be being used to
1405        // document.
1406        cmd.env("RUSTC_BOOTSTRAP", "1");
1407
1408        // If the lib directories are in an unusual location (changed in
1409        // bootstrap.toml), then this needs to explicitly update the dylib search
1410        // path.
1411        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);
1412        let doc_generator_guard =
1413            builder.msg(Kind::Run, "lint-docs", None, self.build_compiler, self.target);
1414        cmd.run(builder);
1415        drop(doc_generator_guard);
1416
1417        // Run rustbook/mdbook to generate the HTML pages.
1418        builder.ensure(RustbookSrc {
1419            target: self.target,
1420            name: "rustc".to_owned(),
1421            src: out_base,
1422            parent: Some(self),
1423            languages: vec![],
1424            build_compiler: None,
1425        });
1426    }
1427}
1428
1429/// Documents the reference.
1430/// It has to always be done using a stage 1+ compiler, because it references in-tree
1431/// compiler/stdlib concepts.
1432#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1433pub struct Reference {
1434    build_compiler: Compiler,
1435    target: TargetSelection,
1436}
1437
1438impl Step for Reference {
1439    type Output = ();
1440
1441    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
1442        run.path("src/doc/reference")
1443    }
1444
1445    fn is_default_step(builder: &Builder<'_>) -> bool {
1446        builder.config.docs
1447    }
1448
1449    fn make_run(run: RunConfig<'_>) {
1450        // Bump the stage to 2, because the reference requires an in-tree compiler.
1451        // At the same time, since this step is enabled by default, we don't want `x doc` to fail
1452        // in stage 1.
1453        // FIXME: create a shared method on builder for auto-bumping, and print some warning when
1454        // it happens.
1455        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {
1456            run.builder.top_stage
1457        } else {
1458            2
1459        };
1460
1461        run.builder.ensure(Reference {
1462            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),
1463            target: run.target,
1464        });
1465    }
1466
1467    /// Builds the reference book.
1468    fn run(self, builder: &Builder<'_>) {
1469        builder.require_submodule("src/doc/reference", None);
1470
1471        // This is needed for generating links to the standard library using
1472        // the mdbook-spec plugin.
1473        builder.std(self.build_compiler, builder.config.host_target);
1474
1475        // Run rustbook/mdbook to generate the HTML pages.
1476        builder.ensure(RustbookSrc {
1477            target: self.target,
1478            name: "reference".to_owned(),
1479            src: builder.src.join("src/doc/reference"),
1480            build_compiler: Some(self.build_compiler),
1481            parent: Some(self),
1482            languages: vec![],
1483        });
1484    }
1485}