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