src/bootstrap/src/core/build_steps/doc.rs RUST 1,507 lines View on github.com → Search inside
1//! Documentation generation for bootstrap.2//!3//! This module implements generation for all bits and pieces of documentation4//! for the Rust project. This notably includes suites like the rust book, the5//! nomicon, rust by example, standalone documentation, etc.6//!7//! Everything here is basically just a shim around calling either `rustbook` or8//! `rustdoc`.910use std::io::{self, Write};11use std::path::{Path, PathBuf};12use std::{env, fs, mem};1314use 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};2425macro_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        }3233        impl Step for $name {34            type Output = ();3536            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {37                run.path($path)38            }3940            fn is_default_step(builder: &Builder<'_>) -> bool {41                builder.config.docs42            }4344            fn make_run(run: RunConfig<'_>) {45                run.builder.ensure($name {46                    target: run.target,47                });48            }4950            fn run(self, builder: &Builder<'_>) {51                if let Some(submodule_path) = submodule_path_of(&builder, $path) {52                    builder.require_submodule(&submodule_path, None)53                }5455                builder.ensure(RustbookSrc {56                    target: self.target,57                    name: $book_name.to_owned(),58                    src: builder.src.join($path),59                    parent: Some(self),60                    languages: $lang.into(),61                    build_compiler: None,62                })63            }64        }65        )+66    }67}6869// NOTE: When adding a book here, make sure to ALSO build the book by70// adding a build step in `src/bootstrap/code/builder/mod.rs`!71// NOTE: Make sure to add the corresponding submodule when adding a new book.72book!(73    CargoBook, "src/tools/cargo/src/doc", "cargo", &[];74    ClippyBook, "src/tools/clippy/book", "clippy", &[];75    EditionGuide, "src/doc/edition-guide", "edition-guide", &[];76    EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[];77    Nomicon, "src/doc/nomicon", "nomicon", &[];78    RustByExample, "src/doc/rust-by-example", "rust-by-example", &["es", "ja", "zh", "ko"];79    RustdocBook, "src/doc/rustdoc", "rustdoc", &[];80    StyleGuide, "src/doc/style-guide", "style-guide", &[];81);8283#[derive(Debug, Clone, Hash, PartialEq, Eq)]84pub struct UnstableBook {85    build_compiler: Compiler,86    target: TargetSelection,87}8889impl Step for UnstableBook {90    type Output = ();9192    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {93        run.path("src/doc/unstable-book")94    }9596    fn is_default_step(builder: &Builder<'_>) -> bool {97        builder.config.docs98    }99100    fn make_run(run: RunConfig<'_>) {101        // Bump the stage to 2, because the unstable book requires an in-tree compiler.102        // At the same time, since this step is enabled by default, we don't want `x doc` to fail103        // in stage 1.104        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {105            run.builder.top_stage106        } else {107            2108        };109110        run.builder.ensure(UnstableBook {111            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),112            target: run.target,113        });114    }115116    fn run(self, builder: &Builder<'_>) {117        builder118            .ensure(UnstableBookGen { build_compiler: self.build_compiler, target: self.target });119        builder.ensure(RustbookSrc {120            target: self.target,121            name: "unstable-book".to_owned(),122            src: builder.md_doc_out(self.target).join("unstable-book"),123            parent: Some(self),124            languages: vec![],125            build_compiler: None,126        })127    }128}129130#[derive(Debug, Clone, Hash, PartialEq, Eq)]131struct RustbookSrc<P: Step> {132    target: TargetSelection,133    name: String,134    src: PathBuf,135    parent: Option<P>,136    languages: Vec<&'static str>,137    /// Compiler whose rustdoc should be used to document things using `mdbook-spec`.138    build_compiler: Option<Compiler>,139}140141impl<P: Step> Step for RustbookSrc<P> {142    type Output = ();143144    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {145        run.never()146    }147148    /// Invoke `rustbook` for `target` for the doc book `name` from the `src` path.149    ///150    /// This will not actually generate any documentation if the documentation has151    /// already been generated.152    fn run(self, builder: &Builder<'_>) {153        let target = self.target;154        let name = self.name;155        let src = self.src;156        let out = builder.doc_out(target);157        t!(fs::create_dir_all(&out));158159        let out = out.join(&name);160        let index = out.join("index.html");161        let rustbook = builder.tool_exe(Tool::Rustbook);162163        if !builder.config.dry_run()164            && (!up_to_date(&src, &index) || !up_to_date(&rustbook, &index))165        {166            builder.info(&format!("Rustbook ({target}) - {name}"));167            let _ = fs::remove_dir_all(&out);168169            let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);170171            if let Some(compiler) = self.build_compiler {172                let mut rustdoc = builder.rustdoc_for_compiler(compiler);173                rustdoc.pop();174                let old_path = env::var_os("PATH").unwrap_or_default();175                let new_path =176                    env::join_paths(std::iter::once(rustdoc).chain(env::split_paths(&old_path)))177                        .expect("could not add rustdoc to PATH");178179                rustbook_cmd.env("PATH", new_path);180                builder.add_rustc_lib_path(compiler, &mut rustbook_cmd);181            }182183            rustbook_cmd184                .arg("build")185                .arg(&src)186                .arg("-d")187                .arg(&out)188                .arg("--rust-root")189                .arg(&builder.src)190                .run(builder);191192            for lang in &self.languages {193                let out = out.join(lang);194195                builder.info(&format!("Rustbook ({target}) - {name} - {lang}"));196                let _ = fs::remove_dir_all(&out);197198                builder199                    .tool_cmd(Tool::Rustbook)200                    .arg("build")201                    .arg(&src)202                    .arg("-d")203                    .arg(&out)204                    .arg("-l")205                    .arg(lang)206                    .run(builder);207            }208        }209210        if self.parent.is_some() {211            builder.maybe_open_in_browser::<P>(index)212        }213    }214215    fn metadata(&self) -> Option<StepMetadata> {216        let mut metadata = StepMetadata::doc(&format!("{} (book)", self.name), self.target);217        if let Some(compiler) = self.build_compiler {218            metadata = metadata.built_by(compiler);219        }220221        Some(metadata)222    }223}224225#[derive(Debug, Clone, Hash, PartialEq, Eq)]226pub struct TheBook {227    /// Compiler whose rustdoc will be used to generated documentation.228    build_compiler: Compiler,229    target: TargetSelection,230}231232impl Step for TheBook {233    type Output = ();234235    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {236        run.path("src/doc/book")237    }238239    fn is_default_step(builder: &Builder<'_>) -> bool {240        builder.config.docs241    }242243    fn make_run(run: RunConfig<'_>) {244        run.builder.ensure(TheBook {245            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),246            target: run.target,247        });248    }249250    /// Builds the book and associated stuff.251    ///252    /// We need to build:253    ///254    /// * Book255    /// * Older edition redirects256    /// * Version info and CSS257    /// * Index page258    /// * Redirect pages259    fn run(self, builder: &Builder<'_>) {260        builder.require_submodule("src/doc/book", None);261262        let build_compiler = self.build_compiler;263        let target = self.target;264265        let absolute_path = builder.src.join("src/doc/book");266        let redirect_path = absolute_path.join("redirects");267268        // build book269        builder.ensure(RustbookSrc {270            target,271            name: "book".to_owned(),272            src: absolute_path.clone(),273            parent: Some(self),274            languages: vec![],275            build_compiler: None,276        });277278        // building older edition redirects279        for edition in &["first-edition", "second-edition", "2018-edition"] {280            builder.ensure(RustbookSrc {281                target,282                name: format!("book/{edition}"),283                src: absolute_path.join(edition),284                // There should only be one book that is marked as the parent for each target, so285                // treat the other editions as not having a parent.286                parent: Option::<Self>::None,287                languages: vec![],288                build_compiler: None,289            });290        }291292        // build the version info page and CSS293        let shared_assets = builder.ensure(SharedAssets { target });294295        // build the redirect pages296        let _guard = builder.msg(Kind::Doc, "book redirect pages", None, build_compiler, target);297        if builder.config.dry_run() {298            return;299        }300301        for file in t!(fs::read_dir(redirect_path)) {302            let file = t!(file);303            let path = file.path();304            let path = path.to_str().unwrap();305306            invoke_rustdoc(builder, build_compiler, &shared_assets, target, path);307        }308    }309}310311fn invoke_rustdoc(312    builder: &Builder<'_>,313    build_compiler: Compiler,314    shared_assets: &SharedAssetsPaths,315    target: TargetSelection,316    markdown: &str,317) {318    let out = builder.doc_out(target);319320    let path = builder.src.join("src/doc").join(markdown);321322    let header = builder.src.join("src/doc/redirect.inc");323    let footer = builder.src.join("src/doc/footer.inc");324325    let mut cmd = builder.rustdoc_cmd(build_compiler);326327    let out = out.join("book");328329    cmd.arg("--html-after-content")330        .arg(&footer)331        .arg("--html-before-content")332        .arg(&shared_assets.version_info)333        .arg("--html-in-header")334        .arg(&header)335        .arg("--markdown-no-toc")336        .arg("--markdown-playground-url")337        .arg("https://play.rust-lang.org/")338        .arg("-o")339        .arg(&out)340        .arg(&path)341        .arg("--markdown-css")342        .arg("../rust.css")343        .arg("-Zunstable-options");344345    if !builder.config.docs_minification {346        cmd.arg("--disable-minification");347    }348349    cmd.run(builder);350}351352#[derive(Debug, Clone, Hash, PartialEq, Eq)]353pub struct Standalone {354    build_compiler: Compiler,355    target: TargetSelection,356}357358impl Step for Standalone {359    type Output = ();360361    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {362        run.path("src/doc").alias("standalone")363    }364365    fn is_default_step(builder: &Builder<'_>) -> bool {366        builder.config.docs367    }368369    fn make_run(run: RunConfig<'_>) {370        run.builder.ensure(Standalone {371            build_compiler: prepare_doc_compiler(372                run.builder,373                run.builder.host_target,374                run.builder.top_stage,375            ),376            target: run.target,377        });378    }379380    /// Generates all standalone documentation as compiled by the rustdoc in `stage`381    /// for the `target` into `out`.382    ///383    /// This will list all of `src/doc` looking for markdown files and appropriately384    /// perform transformations like substituting `VERSION`, `SHORT_HASH`, and385    /// `STAMP` along with providing the various header/footer HTML we've customized.386    ///387    /// In the end, this is just a glorified wrapper around rustdoc!388    fn run(self, builder: &Builder<'_>) {389        let target = self.target;390        let build_compiler = self.build_compiler;391        let _guard = builder.msg(Kind::Doc, "standalone", None, build_compiler, target);392        let out = builder.doc_out(target);393        t!(fs::create_dir_all(&out));394395        let version_info = builder.ensure(SharedAssets { target: self.target }).version_info;396397        let favicon = builder.src.join("src/doc/favicon.inc");398        let footer = builder.src.join("src/doc/footer.inc");399        let full_toc = builder.src.join("src/doc/full-toc.inc");400401        for file in t!(fs::read_dir(builder.src.join("src/doc"))) {402            let file = t!(file);403            let path = file.path();404            let filename = path.file_name().unwrap().to_str().unwrap();405            if !filename.ends_with(".md") || filename == "README.md" {406                continue;407            }408409            let html = out.join(filename).with_extension("html");410            let rustdoc = builder.rustdoc_for_compiler(build_compiler);411            if up_to_date(&path, &html)412                && up_to_date(&footer, &html)413                && up_to_date(&favicon, &html)414                && up_to_date(&full_toc, &html)415                && (builder.config.dry_run() || up_to_date(&version_info, &html))416                && (builder.config.dry_run() || up_to_date(&rustdoc, &html))417            {418                continue;419            }420421            let mut cmd = builder.rustdoc_cmd(build_compiler);422423            cmd.arg("--html-after-content")424                .arg(&footer)425                .arg("--html-before-content")426                .arg(&version_info)427                .arg("--html-in-header")428                .arg(&favicon)429                .arg("--markdown-no-toc")430                .arg("-Zunstable-options")431                .arg("--index-page")432                .arg(builder.src.join("src/doc/index.md"))433                .arg("--markdown-playground-url")434                .arg("https://play.rust-lang.org/")435                .arg("-o")436                .arg(&out)437                .arg(&path);438439            if !builder.config.docs_minification {440                cmd.arg("--disable-minification");441            }442443            if filename == "not_found.md" {444                cmd.arg("--markdown-css").arg("https://doc.rust-lang.org/rust.css");445            } else {446                cmd.arg("--markdown-css").arg("rust.css");447            }448            cmd.run(builder);449        }450451        // We open doc/index.html as the default if invoked as `x.py doc --open`452        // with no particular explicit doc requested (e.g. library/core).453        if builder.paths.is_empty() || builder.was_invoked_explicitly::<Self>(Kind::Doc) {454            let index = out.join("index.html");455            builder.open_in_browser(index);456        }457    }458459    fn metadata(&self) -> Option<StepMetadata> {460        Some(StepMetadata::doc("standalone", self.target).built_by(self.build_compiler))461    }462}463464#[derive(Debug, Clone, Hash, PartialEq, Eq)]465pub struct Releases {466    build_compiler: Compiler,467    target: TargetSelection,468}469470impl Step for Releases {471    type Output = ();472473    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {474        run.path("RELEASES.md").alias("releases")475    }476477    fn is_default_step(builder: &Builder<'_>) -> bool {478        builder.config.docs479    }480481    fn make_run(run: RunConfig<'_>) {482        run.builder.ensure(Releases {483            build_compiler: prepare_doc_compiler(484                run.builder,485                run.builder.host_target,486                run.builder.top_stage,487            ),488            target: run.target,489        });490    }491492    /// Generates HTML release notes to include in the final docs bundle.493    ///494    /// This uses the same stylesheet and other tools as Standalone, but the495    /// RELEASES.md file is included at the root of the repository and gets496    /// the headline added. In the end, the conversion is done by Rustdoc.497    fn run(self, builder: &Builder<'_>) {498        let target = self.target;499        let build_compiler = self.build_compiler;500        let _guard = builder.msg(Kind::Doc, "releases", None, build_compiler, target);501        let out = builder.doc_out(target);502        t!(fs::create_dir_all(&out));503504        builder.ensure(Standalone { build_compiler, target });505506        let version_info = builder.ensure(SharedAssets { target: self.target }).version_info;507508        let favicon = builder.src.join("src/doc/favicon.inc");509        let footer = builder.src.join("src/doc/footer.inc");510        let full_toc = builder.src.join("src/doc/full-toc.inc");511512        let html = out.join("releases.html");513        let tmppath = out.join("releases.md");514        let inpath = builder.src.join("RELEASES.md");515        let rustdoc = builder.rustdoc_for_compiler(build_compiler);516        if !up_to_date(&inpath, &html)517            || !up_to_date(&footer, &html)518            || !up_to_date(&favicon, &html)519            || !up_to_date(&full_toc, &html)520            || !(builder.config.dry_run()521                || up_to_date(&version_info, &html)522                || up_to_date(&rustdoc, &html))523        {524            let mut tmpfile = t!(fs::File::create(&tmppath));525            t!(tmpfile.write_all(b"% Rust Release Notes\n\n"));526            t!(io::copy(&mut t!(fs::File::open(&inpath)), &mut tmpfile));527            mem::drop(tmpfile);528            let mut cmd = builder.rustdoc_cmd(build_compiler);529530            cmd.arg("--html-after-content")531                .arg(&footer)532                .arg("--html-before-content")533                .arg(&version_info)534                .arg("--html-in-header")535                .arg(&favicon)536                .arg("--markdown-no-toc")537                .arg("--markdown-css")538                .arg("rust.css")539                .arg("-Zunstable-options")540                .arg("--index-page")541                .arg(builder.src.join("src/doc/index.md"))542                .arg("--markdown-playground-url")543                .arg("https://play.rust-lang.org/")544                .arg("-o")545                .arg(&out)546                .arg(&tmppath);547548            if !builder.config.docs_minification {549                cmd.arg("--disable-minification");550            }551552            cmd.run(builder);553        }554555        // We open doc/RELEASES.html as the default if invoked as `x.py doc --open RELEASES.md`556        // with no particular explicit doc requested (e.g. library/core).557        if builder.was_invoked_explicitly::<Self>(Kind::Doc) {558            builder.open_in_browser(&html);559        }560    }561562    fn metadata(&self) -> Option<StepMetadata> {563        Some(StepMetadata::doc("releases", self.target).built_by(self.build_compiler))564    }565}566567#[derive(Debug, Clone)]568pub struct SharedAssetsPaths {569    pub version_info: PathBuf,570}571572#[derive(Debug, Clone, Hash, PartialEq, Eq)]573pub struct SharedAssets {574    target: TargetSelection,575}576577impl Step for SharedAssets {578    type Output = SharedAssetsPaths;579580    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {581        // Other tasks depend on this, no need to execute it on its own582        run.never()583    }584585    fn is_default_step(_builder: &Builder<'_>) -> bool {586        false587    }588589    /// Generate shared resources used by other pieces of documentation.590    fn run(self, builder: &Builder<'_>) -> Self::Output {591        let out = builder.doc_out(self.target);592593        let version_input = builder.src.join("src").join("doc").join("version_info.html.template");594        let version_info = out.join("version_info.html");595        if !builder.config.dry_run() && !up_to_date(&version_input, &version_info) {596            let info = t!(fs::read_to_string(&version_input))597                .replace("VERSION", &builder.rust_release())598                .replace("SHORT_HASH", builder.rust_info().sha_short().unwrap_or(""))599                .replace("STAMP", builder.rust_info().sha().unwrap_or(""));600            t!(fs::write(&version_info, info));601        }602603        builder.copy_link(604            &builder.src.join("src").join("doc").join("rust.css"),605            &out.join("rust.css"),606            FileType::Regular,607        );608609        builder.copy_link(610            &builder611                .src612                .join("src")613                .join("librustdoc")614                .join("html")615                .join("static")616                .join("images")617                .join("favicon.svg"),618            &out.join("favicon.svg"),619            FileType::Regular,620        );621        builder.copy_link(622            &builder623                .src624                .join("src")625                .join("librustdoc")626                .join("html")627                .join("static")628                .join("images")629                .join("favicon-32x32.png"),630            &out.join("favicon-32x32.png"),631            FileType::Regular,632        );633634        SharedAssetsPaths { version_info }635    }636}637638/// Document the standard library using `build_compiler`.639#[derive(Debug, Clone, Hash, PartialEq, Eq)]640pub struct Std {641    build_compiler: Compiler,642    target: TargetSelection,643    format: DocumentationFormat,644    crates: Vec<String>,645}646647impl Std {648    pub(crate) fn from_build_compiler(649        build_compiler: Compiler,650        target: TargetSelection,651        format: DocumentationFormat,652    ) -> Self {653        Std { build_compiler, target, format, crates: vec![] }654    }655}656657impl Step for Std {658    /// Path to a directory with the built documentation.659    type Output = PathBuf;660661    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {662        run.crate_or_deps("sysroot").path("library")663    }664665    fn is_default_step(builder: &Builder<'_>) -> bool {666        builder.config.docs667    }668669    fn make_run(run: RunConfig<'_>) {670        let crates = compile::std_crates_for_run_make(&run);671        let target_is_no_std = run.builder.no_std(run.target).unwrap_or(false);672        if crates.is_empty() && target_is_no_std {673            return;674        }675        run.builder.ensure(Std {676            build_compiler: run.builder.compiler_for_std(run.builder.top_stage),677            target: run.target,678            format: if run.builder.config.cmd.json() {679                DocumentationFormat::Json680            } else {681                DocumentationFormat::Html682            },683            crates,684        });685    }686687    /// Compile all standard library documentation.688    ///689    /// This will generate all documentation for the standard library and its690    /// dependencies. This is largely just a wrapper around `cargo doc`.691    fn run(self, builder: &Builder<'_>) -> Self::Output {692        let target = self.target;693        let crates = if self.crates.is_empty() {694            builder695                .in_tree_crates("sysroot", Some(target))696                .iter()697                .map(|c| c.name.to_string())698                .collect()699        } else {700            self.crates701        };702703        let out = match self.format {704            DocumentationFormat::Html => builder.doc_out(target),705            DocumentationFormat::Json => builder.json_doc_out(target),706        };707708        t!(fs::create_dir_all(&out));709710        if self.format == DocumentationFormat::Html {711            builder.ensure(SharedAssets { target: self.target });712        }713714        let index_page = builder715            .src716            .join("src/doc/index.md")717            .into_os_string()718            .into_string()719            .expect("non-utf8 paths are unsupported");720        let mut extra_args = match self.format {721            DocumentationFormat::Html => {722                vec!["--markdown-css", "rust.css", "--markdown-no-toc", "--index-page", &index_page]723            }724            DocumentationFormat::Json => vec!["--output-format", "json"],725        };726727        if !builder.config.docs_minification {728            extra_args.push("--disable-minification");729        }730        // For `--index-page` and `--output-format=json`.731        extra_args.push("-Zunstable-options");732733        doc_std(builder, self.format, self.build_compiler, target, &out, &extra_args, &crates);734735        // Open if the format is HTML736        if let DocumentationFormat::Html = self.format {737            if builder.paths.iter().any(|path| path.ends_with("library")) {738                // For `x.py doc library --open`, open `std` by default.739                let index = out.join("std").join("index.html");740                builder.maybe_open_in_browser::<Self>(index);741            } else {742                for requested_crate in crates {743                    if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) {744                        let index = out.join(requested_crate).join("index.html");745                        builder.maybe_open_in_browser::<Self>(index);746                        break;747                    }748                }749            }750        }751752        out753    }754755    fn metadata(&self) -> Option<StepMetadata> {756        Some(757            StepMetadata::doc("std", self.target)758                .built_by(self.build_compiler)759                .with_metadata(format!("crates=[{}]", self.crates.join(","))),760        )761    }762}763764/// Name of the crates that are visible to consumers of the standard library.765/// Documentation for internal crates is handled by the rustc step, so internal crates will show766/// up there.767///768/// Order here is important!769/// Crates need to be processed starting from the leaves, otherwise rustdoc will not770/// create correct links between crates because rustdoc depends on the771/// existence of the output directories to know if it should be a local772/// or remote link.773const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"];774775#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]776pub enum DocumentationFormat {777    Html,778    Json,779}780781impl DocumentationFormat {782    fn as_str(&self) -> &str {783        match self {784            DocumentationFormat::Html => "HTML",785            DocumentationFormat::Json => "JSON",786        }787    }788}789790/// Build the documentation for public standard library crates.791fn doc_std(792    builder: &Builder<'_>,793    format: DocumentationFormat,794    build_compiler: Compiler,795    target: TargetSelection,796    out: &Path,797    extra_args: &[&str],798    requested_crates: &[String],799) {800    let target_doc_dir_name = if format == DocumentationFormat::Json { "json-doc" } else { "doc" };801    let target_dir =802        builder.stage_out(build_compiler, Mode::Std).join(target).join(target_doc_dir_name);803804    // This is directory where the compiler will place the output of the command.805    // We will then copy the files from this directory into the final `out` directory, the specified806    // as a function parameter.807    let out_dir = target_dir.join(target).join("doc");808809    let mut cargo = builder::Cargo::new(810        builder,811        build_compiler,812        Mode::Std,813        SourceType::InTree,814        target,815        Kind::Doc,816    );817818    compile::std_cargo(builder, target, &mut cargo, requested_crates);819    cargo820        .arg("--no-deps")821        .arg("--target-dir")822        .arg(&*target_dir.to_string_lossy())823        .arg("-Zskip-rustdoc-fingerprint")824        .arg("-Zrustdoc-map")825        .rustdocflag("--extern-html-root-url")826        .rustdocflag("std_detect=https://docs.rs/std_detect/latest/")827        .rustdocflag("--extern-html-root-takes-precedence")828        .rustdocflag("--resource-suffix")829        .rustdocflag(&builder.version);830    for arg in extra_args {831        cargo.rustdocflag(arg);832    }833834    if builder.config.library_docs_private_items {835        cargo.rustdocflag("--document-private-items").rustdocflag("--document-hidden-items");836    }837838    let description =839        format!("library{} in {} format", crate_description(requested_crates), format.as_str());840    let _guard = builder.msg(Kind::Doc, description, Mode::Std, build_compiler, target);841842    cargo.into_cmd().run(builder);843    builder.cp_link_r(&out_dir, out);844}845846/// Prepare a compiler that will be able to document something for `target` at `stage`.847pub fn prepare_doc_compiler(848    builder: &Builder<'_>,849    target: TargetSelection,850    stage: u32,851) -> Compiler {852    assert!(stage > 0, "Cannot document anything in stage 0");853    let build_compiler = builder.compiler(stage - 1, builder.host_target);854    builder.std(build_compiler, target);855    build_compiler856}857858/// Document the compiler for the given `target` using rustdoc from `build_compiler`.859#[derive(Debug, Clone, Hash, PartialEq, Eq)]860pub struct Rustc {861    build_compiler: Compiler,862    target: TargetSelection,863    crates: Vec<String>,864}865866impl Rustc {867    /// Document `stage` compiler for the given `target`.868    pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self {869        let build_compiler = prepare_doc_compiler(builder, target, stage);870        Self::from_build_compiler(builder, build_compiler, target)871    }872873    fn from_build_compiler(874        builder: &Builder<'_>,875        build_compiler: Compiler,876        target: TargetSelection,877    ) -> Self {878        let crates = builder879            .in_tree_crates("rustc-main", Some(target))880            .into_iter()881            .map(|krate| krate.name.to_string())882            .collect();883        Self { build_compiler, target, crates }884    }885}886887impl Step for Rustc {888    type Output = ();889    const IS_HOST: bool = true;890891    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {892        run.crate_or_deps("rustc-main").path("compiler")893    }894895    fn is_default_step(builder: &Builder<'_>) -> bool {896        builder.config.compiler_docs897    }898899    fn make_run(run: RunConfig<'_>) {900        run.builder.ensure(Rustc::for_stage(run.builder, run.builder.top_stage, run.target));901    }902903    /// Generates compiler documentation.904    ///905    /// This will generate all documentation for compiler and dependencies.906    /// Compiler documentation is distributed separately, so we make sure907    /// we do not merge it with the other documentation from std, test and908    /// proc_macros. This is largely just a wrapper around `cargo doc`.909    fn run(self, builder: &Builder<'_>) {910        let target = self.target;911912        // This is the intended out directory for compiler documentation.913        let out = builder.compiler_doc_out(target);914        t!(fs::create_dir_all(&out));915916        // Build the standard library, so that proc-macros can use it.917        // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.)918        let build_compiler = self.build_compiler;919        builder.std(build_compiler, builder.config.host_target);920921        let _guard = builder.msg(922            Kind::Doc,923            format!("compiler{}", crate_description(&self.crates)),924            Mode::Rustc,925            build_compiler,926            target,927        );928929        // Build cargo command.930        let mut cargo = builder::Cargo::new(931            builder,932            build_compiler,933            Mode::Rustc,934            SourceType::InTree,935            target,936            Kind::Doc,937        );938939        cargo.rustdocflag("--document-private-items");940        // Since we always pass --document-private-items, there's no need to warn about linking to private items.941        cargo.rustdocflag("-Arustdoc::private-intra-doc-links");942        cargo.rustdocflag("--enable-index-page");943        cargo.rustdocflag("-Znormalize-docs");944        cargo.rustdocflag("--show-type-layout");945        // FIXME: `--generate-link-to-definition` tries to resolve cfged out code946        // see https://github.com/rust-lang/rust/pull/122066#issuecomment-1983049222947        // If there is any bug, please comment out the next line.948        cargo.rustdocflag("--generate-link-to-definition");949        cargo.rustdocflag("--generate-macro-expansion");950951        compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);952        cargo.arg("-Zskip-rustdoc-fingerprint");953954        // Only include compiler crates, no dependencies of those, such as `libc`.955        // Do link to dependencies on `docs.rs` however using `rustdoc-map`.956        cargo.arg("--no-deps");957        cargo.arg("-Zrustdoc-map");958959        // FIXME: `-Zrustdoc-map` does not yet correctly work for transitive dependencies,960        // once this is no longer an issue the special case for `ena` can be removed.961        cargo.rustdocflag("--extern-html-root-url");962        cargo.rustdocflag("ena=https://docs.rs/ena/latest/");963964        let mut to_open = None;965966        let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc");967        for krate in &*self.crates {968            // Create all crate output directories first to make sure rustdoc uses969            // relative links.970            // FIXME: Cargo should probably do this itself.971            let dir_name = krate.replace('-', "_");972            t!(fs::create_dir_all(out_dir.join(&*dir_name)));973            cargo.arg("-p").arg(krate);974            if to_open.is_none() {975                to_open = Some(dir_name);976            }977        }978979        // This uses a shared directory so that librustdoc documentation gets980        // correctly built and merged with the rustc documentation.981        //982        // This is needed because rustdoc is built in a different directory from983        // rustc. rustdoc needs to be able to see everything, for example when984        // merging the search index, or generating local (relative) links.985        symlink_dir_force(&builder.config, &out, &out_dir);986        // Cargo puts proc macros in `target/doc` even if you pass `--target`987        // explicitly (https://github.com/rust-lang/cargo/issues/7677).988        let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc");989        symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);990991        cargo.into_cmd().run(builder);992993        if !builder.config.dry_run() {994            // Sanity check on linked compiler crates995            for krate in &*self.crates {996                let dir_name = krate.replace('-', "_");997                // Making sure the directory exists and is not empty.998                assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some());999            }1000        }10011002        if builder.paths.iter().any(|path| path.ends_with("compiler")) {1003            // For `x.py doc compiler --open`, open `rustc_middle` by default.1004            let index = out.join("rustc_middle").join("index.html");1005            builder.open_in_browser(index);1006        } else if let Some(krate) = to_open {1007            // Let's open the first crate documentation page:1008            let index = out.join(krate).join("index.html");1009            builder.open_in_browser(index);1010        }1011    }10121013    fn metadata(&self) -> Option<StepMetadata> {1014        Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler))1015    }1016}10171018macro_rules! tool_doc {1019    (1020        $tool: ident,1021        $path: literal,1022        mode = $mode:expr1023        $(, is_library = $is_library:expr )?1024        $(, crates = $crates:expr )?1025        // Subset of nightly features that are allowed to be used when documenting1026        $(, allow_features: $allow_features:expr )?1027       ) => {1028        #[derive(Debug, Clone, Hash, PartialEq, Eq)]1029        pub struct $tool {1030            build_compiler: Compiler,1031            mode: Mode,1032            target: TargetSelection,1033        }10341035        impl Step for $tool {1036            type Output = ();1037            const IS_HOST: bool = true;10381039            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {1040                run.path($path)1041            }10421043            fn is_default_step(builder: &Builder<'_>) -> bool {1044                builder.config.compiler_docs1045            }10461047            fn make_run(run: RunConfig<'_>) {1048                let target = run.target;1049                let build_compiler = match $mode {1050                    Mode::ToolRustcPrivate => {1051                        // Rustdoc needs the rustc sysroot available to build.1052                        let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target);10531054                        // Build rustc docs so that we generate relative links.1055                        run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target));1056                        compilers.build_compiler()1057                    }1058                    Mode::ToolTarget => {1059                        // when shipping multiple docs together in one folder,1060                        // they all need to use the same rustdoc version1061                        prepare_doc_compiler(run.builder, run.builder.host_target, run.builder.top_stage)1062                    }1063                    _ => {1064                        panic!("Unexpected tool mode for documenting: {:?}", $mode);1065                    }1066                };10671068                run.builder.ensure($tool { build_compiler, mode: $mode, target });1069            }10701071            /// Generates documentation for a tool.1072            ///1073            /// This is largely just a wrapper around `cargo doc`.1074            fn run(self, builder: &Builder<'_>) {1075                let mut source_type = SourceType::InTree;10761077                if let Some(submodule_path) = submodule_path_of(&builder, $path) {1078                    source_type = SourceType::Submodule;1079                    builder.require_submodule(&submodule_path, None);1080                }10811082                let $tool { build_compiler, mode, target } = self;10831084                // This is the intended out directory for compiler documentation.1085                let out = builder.compiler_doc_out(target);1086                t!(fs::create_dir_all(&out));10871088                // Build cargo command.1089                let mut cargo = prepare_tool_cargo(1090                    builder,1091                    build_compiler,1092                    mode,1093                    target,1094                    Kind::Doc,1095                    $path,1096                    source_type,1097                    &[],1098                );1099                let allow_features = {1100                    let mut _value = "";1101                    $( _value = $allow_features; )?1102                    _value1103                };11041105                if !allow_features.is_empty() {1106                    cargo.allow_features(allow_features);1107                }11081109                cargo.arg("-Zskip-rustdoc-fingerprint");1110                // Only include compiler crates, no dependencies of those, such as `libc`.1111                cargo.arg("--no-deps");11121113                if false $(|| $is_library)? {1114                    cargo.arg("--lib");1115                }11161117                $(for krate in $crates {1118                    cargo.arg("-p").arg(krate);1119                })?11201121                cargo.rustdocflag("--document-private-items");1122                // Since we always pass --document-private-items, there's no need to warn about linking to private items.1123                cargo.rustdocflag("-Arustdoc::private-intra-doc-links");1124                cargo.rustdocflag("--enable-index-page");1125                cargo.rustdocflag("--show-type-layout");1126                cargo.rustdocflag("--generate-link-to-definition");11271128                let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc");1129                $(for krate in $crates {1130                    let dir_name = krate.replace("-", "_");1131                    t!(fs::create_dir_all(out_dir.join(&*dir_name)));1132                })?11331134                // Symlink compiler docs to the output directory of rustdoc documentation.1135                symlink_dir_force(&builder.config, &out, &out_dir);1136                let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc");1137                symlink_dir_force(&builder.config, &out, &proc_macro_out_dir);11381139                let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target);1140                cargo.into_cmd().run(builder);11411142                if !builder.config.dry_run() {1143                    // Sanity check on linked doc directories1144                    $(for krate in $crates {1145                        let dir_name = krate.replace("-", "_");1146                        // Making sure the directory exists and is not empty.1147                        assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some());1148                    })?1149                }1150            }11511152            fn metadata(&self) -> Option<StepMetadata> {1153                Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler))1154            }1155        }1156    }1157}11581159// NOTE: make sure to register these in `Builder::get_step_description`.1160tool_doc!(1161    BuildHelper,1162    "src/build_helper",1163    // ideally, this would use ToolBootstrap,1164    // but we distribute these docs together in the same folder1165    // as a bunch of stage1 tools, and you can't mix rustdoc versions1166    // because that breaks cross-crate data (particularly search)1167    mode = Mode::ToolTarget,1168    is_library = true,1169    crates = ["build_helper"]1170);1171tool_doc!(1172    Rustdoc,1173    "src/tools/rustdoc",1174    mode = Mode::ToolRustcPrivate,1175    crates = ["rustdoc", "rustdoc-json-types"]1176);1177tool_doc!(1178    Rustfmt,1179    "src/tools/rustfmt",1180    mode = Mode::ToolRustcPrivate,1181    crates = ["rustfmt-nightly", "rustfmt-config_proc_macro"]1182);1183tool_doc!(1184    Clippy,1185    "src/tools/clippy",1186    mode = Mode::ToolRustcPrivate,1187    crates = ["clippy_config", "clippy_utils"]1188);1189tool_doc!(Miri, "src/tools/miri", mode = Mode::ToolRustcPrivate, crates = ["miri"]);1190tool_doc!(1191    Cargo,1192    "src/tools/cargo",1193    mode = Mode::ToolTarget,1194    crates = [1195        "cargo",1196        "cargo-credential",1197        "cargo-platform",1198        "cargo-test-macro",1199        "cargo-test-support",1200        "cargo-util",1201        "cargo-util-schemas",1202        "crates-io",1203        "mdman",1204        "rustfix",1205    ],1206    // Required because of the im-rc dependency of Cargo, which automatically opts into the1207    // "specialization" feature in its build script when it detects a nightly toolchain.1208    allow_features: "specialization"1209);1210tool_doc!(Tidy, "src/tools/tidy", mode = Mode::ToolTarget, crates = ["tidy"]);1211tool_doc!(1212    Bootstrap,1213    "src/bootstrap",1214    mode = Mode::ToolTarget,1215    is_library = true,1216    crates = ["bootstrap"]1217);1218tool_doc!(1219    RunMakeSupport,1220    "src/tools/run-make-support",1221    mode = Mode::ToolTarget,1222    is_library = true,1223    crates = ["run_make_support"]1224);1225tool_doc!(1226    Compiletest,1227    "src/tools/compiletest",1228    mode = Mode::ToolTarget,1229    is_library = true,1230    crates = ["compiletest"]1231);12321233#[derive(Debug, Clone, Hash, PartialEq, Eq)]1234pub struct ErrorIndex {1235    compilers: RustcPrivateCompilers,1236}12371238impl Step for ErrorIndex {1239    type Output = ();1240    const IS_HOST: bool = true;12411242    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {1243        run.path("src/tools/error_index_generator")1244    }12451246    fn is_default_step(builder: &Builder<'_>) -> bool {1247        builder.config.docs1248    }12491250    fn make_run(run: RunConfig<'_>) {1251        run.builder.ensure(ErrorIndex {1252            compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target),1253        });1254    }12551256    /// Generates the HTML rendered error-index by running the1257    /// `error_index_generator` tool.1258    fn run(self, builder: &Builder<'_>) {1259        builder.info(&format!("Documenting error index ({})", self.compilers.target()));1260        let out = builder.doc_out(self.compilers.target());1261        t!(fs::create_dir_all(&out));1262        tool::ErrorIndex::command(builder, self.compilers)1263            .arg("html")1264            .arg(&out)1265            .arg(&builder.version)1266            .run(builder);12671268        let index = out.join("error-index.html");1269        builder.maybe_open_in_browser::<Self>(index);1270    }12711272    fn metadata(&self) -> Option<StepMetadata> {1273        Some(1274            StepMetadata::doc("error-index", self.compilers.target())1275                .built_by(self.compilers.build_compiler()),1276        )1277    }1278}12791280#[derive(Debug, Clone, Hash, PartialEq, Eq)]1281pub struct UnstableBookGen {1282    build_compiler: Compiler,1283    target: TargetSelection,1284}12851286impl Step for UnstableBookGen {1287    type Output = ();1288    const IS_HOST: bool = true;12891290    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {1291        run.path("src/tools/unstable-book-gen")1292    }12931294    fn is_default_step(builder: &Builder<'_>) -> bool {1295        builder.config.docs1296    }12971298    fn make_run(run: RunConfig<'_>) {1299        run.builder.ensure(UnstableBookGen {1300            build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage),1301            target: run.target,1302        });1303    }13041305    fn run(self, builder: &Builder<'_>) {1306        let target = self.target;1307        let rustc_path = builder.rustc(self.build_compiler);13081309        builder.info(&format!("Generating unstable book md files ({target})"));1310        let out = builder.md_doc_out(target).join("unstable-book");1311        builder.create_dir(&out);1312        builder.remove_dir(&out);1313        let mut cmd = builder.tool_cmd(Tool::UnstableBookGen);1314        cmd.arg(builder.src.join("library"));1315        cmd.arg(builder.src.join("compiler"));1316        cmd.arg(builder.src.join("src"));1317        cmd.arg(rustc_path);1318        cmd.arg(out);13191320        cmd.run(builder);1321    }1322}13231324fn symlink_dir_force(config: &Config, original: &Path, link: &Path) {1325    if config.dry_run() {1326        return;1327    }1328    if let Ok(m) = fs::symlink_metadata(link) {1329        if m.file_type().is_dir() {1330            t!(fs::remove_dir_all(link));1331        } else {1332            // handle directory junctions on windows by falling back to1333            // `remove_dir`.1334            t!(fs::remove_file(link).or_else(|_| fs::remove_dir(link)));1335        }1336    }13371338    t!(1339        symlink_dir(config, original, link),1340        format!("failed to create link from {} -> {}", link.display(), original.display())1341    );1342}13431344/// Builds the Rust compiler book.1345#[derive(Debug, Clone, Hash, PartialEq, Eq)]1346pub struct RustcBook {1347    build_compiler: Compiler,1348    target: TargetSelection,1349    /// Test that the examples of lints in the book produce the correct lints in the expected1350    /// format.1351    validate: bool,1352}13531354impl RustcBook {1355    pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self {1356        Self { build_compiler, target, validate: true }1357    }1358}13591360impl Step for RustcBook {1361    type Output = ();1362    const IS_HOST: bool = true;13631364    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {1365        run.path("src/doc/rustc")1366    }13671368    fn is_default_step(builder: &Builder<'_>) -> bool {1369        builder.config.docs1370    }13711372    fn make_run(run: RunConfig<'_>) {1373        // Bump the stage to 2, because the rustc book requires an in-tree compiler.1374        // At the same time, since this step is enabled by default, we don't want `x doc` to fail1375        // in stage 1.1376        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {1377            run.builder.top_stage1378        } else {1379            21380        };13811382        run.builder.ensure(RustcBook {1383            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),1384            target: run.target,1385            validate: false,1386        });1387    }13881389    /// Builds the rustc book.1390    ///1391    /// The lints are auto-generated by a tool, and then merged into the book1392    /// in the "md-doc" directory in the build output directory. Then1393    /// "rustbook" is used to convert it to HTML.1394    fn run(self, builder: &Builder<'_>) {1395        let out_base = builder.md_doc_out(self.target).join("rustc");1396        t!(fs::create_dir_all(&out_base));1397        let out_listing = out_base.join("src/lints");1398        builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base);1399        builder.info(&format!("Generating lint docs ({})", self.target));14001401        let rustc = builder.rustc(self.build_compiler);1402        // The tool runs `rustc` for extracting output examples, so it needs a1403        // functional sysroot.1404        builder.std(self.build_compiler, self.target);1405        let mut cmd = builder.tool_cmd(Tool::LintDocs);1406        cmd.arg("--build-rustc-stage");1407        cmd.arg(self.build_compiler.stage.to_string());1408        cmd.arg("--src");1409        cmd.arg(builder.src.join("compiler"));1410        cmd.arg("--out");1411        cmd.arg(&out_listing);1412        cmd.arg("--rustc");1413        cmd.arg(&rustc);1414        cmd.arg("--rustc-target").arg(self.target.rustc_target_arg());1415        if let Some(target_linker) = builder.linker(self.target) {1416            cmd.arg("--rustc-linker").arg(target_linker);1417        }1418        if builder.is_verbose() {1419            cmd.arg("--verbose");1420        }1421        if self.validate {1422            cmd.arg("--validate");1423        }1424        // We need to validate nightly features, even on the stable channel.1425        // Set this unconditionally as the stage0 compiler may be being used to1426        // document.1427        cmd.env("RUSTC_BOOTSTRAP", "1");14281429        // If the lib directories are in an unusual location (changed in1430        // bootstrap.toml), then this needs to explicitly update the dylib search1431        // path.1432        builder.add_rustc_lib_path(self.build_compiler, &mut cmd);1433        let doc_generator_guard =1434            builder.msg(Kind::Run, "lint-docs", None, self.build_compiler, self.target);1435        cmd.run(builder);1436        drop(doc_generator_guard);14371438        // Run rustbook/mdbook to generate the HTML pages.1439        builder.ensure(RustbookSrc {1440            target: self.target,1441            name: "rustc".to_owned(),1442            src: out_base,1443            parent: Some(self),1444            languages: vec![],1445            build_compiler: None,1446        });1447    }1448}14491450/// Documents the reference.1451/// It has to always be done using a stage 1+ compiler, because it references in-tree1452/// compiler/stdlib concepts.1453#[derive(Debug, Clone, Hash, PartialEq, Eq)]1454pub struct Reference {1455    build_compiler: Compiler,1456    target: TargetSelection,1457}14581459impl Step for Reference {1460    type Output = ();14611462    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {1463        run.path("src/doc/reference")1464    }14651466    fn is_default_step(builder: &Builder<'_>) -> bool {1467        builder.config.docs1468    }14691470    fn make_run(run: RunConfig<'_>) {1471        // Bump the stage to 2, because the reference requires an in-tree compiler.1472        // At the same time, since this step is enabled by default, we don't want `x doc` to fail1473        // in stage 1.1474        // FIXME: create a shared method on builder for auto-bumping, and print some warning when1475        // it happens.1476        let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 {1477            run.builder.top_stage1478        } else {1479            21480        };14811482        run.builder.ensure(Reference {1483            build_compiler: prepare_doc_compiler(run.builder, run.target, stage),1484            target: run.target,1485        });1486    }14871488    /// Builds the reference book.1489    fn run(self, builder: &Builder<'_>) {1490        builder.require_submodule("src/doc/reference", None);14911492        // This is needed for generating links to the standard library using1493        // the mdbook-spec plugin.1494        builder.std(self.build_compiler, builder.config.host_target);14951496        // Run rustbook/mdbook to generate the HTML pages.1497        builder.ensure(RustbookSrc {1498            target: self.target,1499            name: "reference".to_owned(),1500            src: builder.src.join("src/doc/reference"),1501            build_compiler: Some(self.build_compiler),1502            parent: Some(self),1503            languages: vec![],1504        });1505    }1506}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.