about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLuca Palmieri <rust@lpalmieri.com>2022-09-14 13:49:05 +0200
committerLuca Palmieri <rust@lpalmieri.com>2022-09-17 21:58:34 +0200
commit235dccef2b8012e7d9da87f4b6091912d802cff2 (patch)
tree5bc6336a2eaba7770223d3e83a724656ef53c78f /src
parent9da4644d5685aa0c4daa4aea6ddc9eb834ae51cc (diff)
downloadrust-235dccef2b8012e7d9da87f4b6091912d802cff2.tar.gz
rust-235dccef2b8012e7d9da87f4b6091912d802cff2.zip
Add a new component, `rust-json-docs`, to distribute the JSON-formatted documentation for std crates in nightly toolchains.
We also add a new flag to `x doc`, `--json`, to render the JSON-formatted version alongside the HTML-formatted one.
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/builder.rs1
-rw-r--r--src/bootstrap/builder/tests.rs4
-rw-r--r--src/bootstrap/dist.rs39
-rw-r--r--src/bootstrap/doc.rs212
-rw-r--r--src/bootstrap/flags.rs20
-rw-r--r--src/bootstrap/lib.rs5
6 files changed, 219 insertions, 62 deletions
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 14e8ebd6876..3d5933f1f14 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -708,6 +708,7 @@ impl<'a> Builder<'a> {
             Kind::Dist => describe!(
                 dist::Docs,
                 dist::RustcDocs,
+                dist::JsonDocs,
                 dist::Mingw,
                 dist::Rustc,
                 dist::Std,
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index 280eba75f0c..88bbcc93d07 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -236,7 +236,7 @@ mod defaults {
     fn doc_default() {
         let mut config = configure("doc", &["A"], &["A"]);
         config.compiler_docs = true;
-        config.cmd = Subcommand::Doc { paths: Vec::new(), open: false };
+        config.cmd = Subcommand::Doc { paths: Vec::new(), open: false, json: false };
         let mut cache = run_build(&[], config);
         let a = TargetSelection::from_user("A");
 
@@ -587,7 +587,7 @@ mod dist {
     fn doc_ci() {
         let mut config = configure(&["A"], &["A"]);
         config.compiler_docs = true;
-        config.cmd = Subcommand::Doc { paths: Vec::new(), open: false };
+        config.cmd = Subcommand::Doc { paths: Vec::new(), open: false, json: false };
         let build = Build::new(config);
         let mut builder = Builder::new(&build);
         builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Doc), &[]);
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 1a59b3958f1..c9ee3c1c7d6 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -87,6 +87,45 @@ impl Step for Docs {
     }
 }
 
+#[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct JsonDocs {
+    pub host: TargetSelection,
+}
+
+impl Step for JsonDocs {
+    type Output = Option<GeneratedTarball>;
+    const DEFAULT: bool = true;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        let default = run.builder.config.docs;
+        run.alias("rust-json-docs").default_condition(default)
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(JsonDocs { host: run.target });
+    }
+
+    /// Builds the `rust-json-docs` installer component.
+    fn run(self, builder: &Builder<'_>) -> Option<GeneratedTarball> {
+        // This prevents JSON docs from being built for "dist" or "install"
+        // on the stable/beta channels. The JSON format is not stable yet and
+        // should not be included in stable/beta toolchains.
+        if !builder.build.unstable_features() {
+            return None;
+        }
+
+        let host = self.host;
+        builder.ensure(crate::doc::JsonStd { stage: builder.top_stage, target: host });
+
+        let dest = "share/doc/rust/json";
+
+        let mut tarball = Tarball::new(builder, "rust-json-docs", &host.triple);
+        tarball.set_product_name("Rust Documentation In JSON Format");
+        tarball.add_bulk_dir(&builder.json_doc_out(host), dest);
+        Some(tarball.generate())
+    }
+}
+
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct RustcDocs {
     pub host: TargetSelection,
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index f909ecc0ab8..a54481f39e0 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -7,6 +7,7 @@
 //! Everything here is basically just a shim around calling either `rustbook` or
 //! `rustdoc`.
 
+use std::ffi::OsStr;
 use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
@@ -431,49 +432,24 @@ impl Step for Std {
     fn run(self, builder: &Builder<'_>) {
         let stage = self.stage;
         let target = self.target;
-        builder.info(&format!("Documenting stage{} std ({})", stage, target));
-        if builder.no_std(target) == Some(true) {
-            panic!(
-                "building std documentation for no_std target {target} is not supported\n\
-                 Set `docs = false` in the config to disable documentation."
-            );
-        }
         let out = builder.doc_out(target);
         t!(fs::create_dir_all(&out));
-        let compiler = builder.compiler(stage, builder.config.build);
-
-        let out_dir = builder.stage_out(compiler, Mode::Std).join(target.triple).join("doc");
-
         t!(fs::copy(builder.src.join("src/doc/rust.css"), out.join("rust.css")));
 
-        let run_cargo_rustdoc_for = |package: &str| {
-            let mut cargo =
-                builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "rustdoc");
-            compile::std_cargo(builder, target, compiler.stage, &mut cargo);
-
-            cargo
-                .arg("-p")
-                .arg(package)
-                .arg("-Zskip-rustdoc-fingerprint")
-                .arg("--")
-                .arg("--markdown-css")
-                .arg("rust.css")
-                .arg("--markdown-no-toc")
-                .arg("-Z")
-                .arg("unstable-options")
-                .arg("--resource-suffix")
-                .arg(&builder.version)
-                .arg("--index-page")
-                .arg(&builder.src.join("src/doc/index.md"));
-
-            if !builder.config.docs_minification {
-                cargo.arg("--disable-minification");
-            }
-
-            builder.run(&mut cargo.into());
-        };
+        let index_page = builder.src.join("src/doc/index.md").into_os_string();
+        let mut extra_args = vec![
+            OsStr::new("--markdown-css"),
+            OsStr::new("rust.css"),
+            OsStr::new("--markdown-no-toc"),
+            OsStr::new("--index-page"),
+            &index_page,
+        ];
+
+        if !builder.config.docs_minification {
+            extra_args.push(OsStr::new("--disable-minification"));
+        }
 
-        let paths = builder
+        let requested_crates = builder
             .paths
             .iter()
             .map(components_simplified)
@@ -491,30 +467,20 @@ impl Step for Std {
             })
             .collect::<Vec<_>>();
 
-        // Only build the following crates. While we could just iterate over the
-        // folder structure, that would also build internal crates that we do
-        // not want to show in documentation. These crates will later be visited
-        // by the rustc step, so internal documentation will show them.
-        //
-        // Note that the order here is important! The crates need to be
-        // processed starting from the leaves, otherwise rustdoc will not
-        // create correct links between crates because rustdoc depends on the
-        // existence of the output directories to know if it should be a local
-        // or remote link.
-        let krates = ["core", "alloc", "std", "proc_macro", "test"];
-        for krate in &krates {
-            run_cargo_rustdoc_for(krate);
-            if paths.iter().any(|p| p == krate) {
-                // No need to document more of the libraries if we have the one we want.
-                break;
-            }
-        }
-        builder.cp_r(&out_dir, &out);
+        doc_std(
+            builder,
+            DocumentationFormat::HTML,
+            stage,
+            target,
+            &out,
+            &extra_args,
+            &requested_crates,
+        );
 
         // Look for library/std, library/core etc in the `x.py doc` arguments and
         // open the corresponding rendered docs.
-        for requested_crate in paths {
-            if krates.iter().any(|k| *k == requested_crate.as_str()) {
+        for requested_crate in requested_crates {
+            if STD_PUBLIC_CRATES.iter().any(|k| *k == requested_crate.as_str()) {
                 let index = out.join(requested_crate).join("index.html");
                 open(builder, &index);
             }
@@ -523,6 +489,134 @@ impl Step for Std {
 }
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct JsonStd {
+    pub stage: u32,
+    pub target: TargetSelection,
+}
+
+impl Step for JsonStd {
+    type Output = ();
+    const DEFAULT: bool = false;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        let default = run.builder.config.docs && run.builder.config.cmd.json();
+        run.all_krates("test").path("library").default_condition(default)
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(Std { stage: run.builder.top_stage, target: run.target });
+    }
+
+    /// Build JSON documentation for the standard library crates.
+    ///
+    /// This is largely just a wrapper around `cargo doc`.
+    fn run(self, builder: &Builder<'_>) {
+        let stage = self.stage;
+        let target = self.target;
+        let out = builder.json_doc_out(target);
+        t!(fs::create_dir_all(&out));
+        let extra_args = [OsStr::new("--output-format"), OsStr::new("json")];
+        doc_std(builder, DocumentationFormat::JSON, stage, target, &out, &extra_args, &[])
+    }
+}
+
+/// Name of the crates that are visible to consumers of the standard library.
+/// Documentation for internal crates is handled by the rustc step, so internal crates will show
+/// up there.
+///
+/// Order here is important!
+/// Crates need to be processed starting from the leaves, otherwise rustdoc will not
+/// create correct links between crates because rustdoc depends on the
+/// existence of the output directories to know if it should be a local
+/// or remote link.
+const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"];
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+enum DocumentationFormat {
+    HTML,
+    JSON,
+}
+
+impl DocumentationFormat {
+    fn as_str(&self) -> &str {
+        match self {
+            DocumentationFormat::HTML => "HTML",
+            DocumentationFormat::JSON => "JSON",
+        }
+    }
+}
+
+/// Build the documentation for public standard library crates.
+///
+/// `requested_crates` can be used to build only a subset of the crates. If empty, all crates will
+/// be built.
+fn doc_std(
+    builder: &Builder<'_>,
+    format: DocumentationFormat,
+    stage: u32,
+    target: TargetSelection,
+    out: &Path,
+    extra_args: &[&OsStr],
+    requested_crates: &[String],
+) {
+    builder.info(&format!(
+        "Documenting stage{} std ({}) in {} format",
+        stage,
+        target,
+        format.as_str()
+    ));
+    if builder.no_std(target) == Some(true) {
+        panic!(
+            "building std documentation for no_std target {target} is not supported\n\
+             Set `docs = false` in the config to disable documentation."
+        );
+    }
+    let compiler = builder.compiler(stage, builder.config.build);
+    // This is directory where the compiler will place the output of the command.
+    // We will then copy the files from this directory into the final `out` directory, the specified
+    // as a function parameter.
+    let out_dir = builder.stage_out(compiler, Mode::Std).join(target.triple).join("doc");
+    // `cargo` uses the same directory for both JSON docs and HTML docs.
+    // This could lead to cross-contamination when copying files into the specified `out` directory.
+    // For example:
+    // ```bash
+    // x doc std
+    // x doc std --json
+    // ```
+    // could lead to HTML docs being copied into the JSON docs output directory.
+    // To avoid this issue, we clean the doc folder before invoking `cargo`.
+    if out_dir.exists() {
+        builder.remove_dir(&out_dir);
+    }
+
+    let run_cargo_rustdoc_for = |package: &str| {
+        let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "rustdoc");
+        compile::std_cargo(builder, target, compiler.stage, &mut cargo);
+        cargo
+            .arg("-p")
+            .arg(package)
+            .arg("-Zskip-rustdoc-fingerprint")
+            .arg("--")
+            .arg("-Z")
+            .arg("unstable-options")
+            .arg("--resource-suffix")
+            .arg(&builder.version)
+            .args(extra_args);
+        builder.run(&mut cargo.into());
+    };
+
+    for krate in STD_PUBLIC_CRATES {
+        run_cargo_rustdoc_for(krate);
+        if requested_crates.iter().any(|p| p == krate) {
+            // No need to document more of the libraries if we have the one we want.
+            break;
+        }
+    }
+
+    builder.cp_r(&out_dir, &out);
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
 pub struct Rustc {
     pub stage: u32,
     pub target: TargetSelection,
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index 789da748100..802b49d748a 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -107,6 +107,7 @@ pub enum Subcommand {
     Doc {
         paths: Vec<PathBuf>,
         open: bool,
+        json: bool,
     },
     Test {
         paths: Vec<PathBuf>,
@@ -325,6 +326,11 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
             }
             Kind::Doc => {
                 opts.optflag("", "open", "open the docs in a browser");
+                opts.optflag(
+                    "",
+                    "json",
+                    "render the documentation in JSON format in addition to the usual HTML format",
+                );
             }
             Kind::Clean => {
                 opts.optflag("", "all", "clean all build artifacts");
@@ -493,6 +499,7 @@ Arguments:
         ./x.py doc src/doc/book
         ./x.py doc src/doc/nomicon
         ./x.py doc src/doc/book library/std
+        ./x.py doc library/std --json
         ./x.py doc library/std --open
 
     If no arguments are passed then everything is documented:
@@ -581,7 +588,11 @@ Arguments:
                 },
             },
             Kind::Bench => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
-            Kind::Doc => Subcommand::Doc { paths, open: matches.opt_present("open") },
+            Kind::Doc => Subcommand::Doc {
+                paths,
+                open: matches.opt_present("open"),
+                json: matches.opt_present("json"),
+            },
             Kind::Clean => {
                 if !paths.is_empty() {
                     println!("\nclean does not take a path argument\n");
@@ -787,6 +798,13 @@ impl Subcommand {
             _ => false,
         }
     }
+
+    pub fn json(&self) -> bool {
+        match *self {
+            Subcommand::Doc { json, .. } => json,
+            _ => false,
+        }
+    }
 }
 
 fn split(s: &[String]) -> Vec<String> {
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index cc0cf12bd18..868ec10bc15 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -825,6 +825,11 @@ impl Build {
         self.out.join(&*target.triple).join("doc")
     }
 
+    /// Output directory for all JSON-formatted documentation for a target
+    fn json_doc_out(&self, target: TargetSelection) -> PathBuf {
+        self.out.join(&*target.triple).join("json-doc")
+    }
+
     fn test_out(&self, target: TargetSelection) -> PathBuf {
         self.out.join(&*target.triple).join("test")
     }