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