From 9276399342c32b87a0126e3ef88864c6aa1bc576 Mon Sep 17 00:00:00 2001
From: Charalampos Mitrodimas
Date: Sat, 10 Jun 2023 21:22:12 +0300
Subject: [PATCH] Add unstable `--output-format` option to "rustdoc"
We achieved this by:
* Handle `--output-format` argument, accepting `html` or `json`
* If `json` is passed, we append the following in
`prepare_rustdoc`:
1. `-Zunstable-options`
2. `--output-format`
Fixes #12103
Signed-off-by: Charalampos Mitrodimas
---
src/bin/cargo/commands/doc.rs | 2 +
src/bin/cargo/commands/rustdoc.rs | 24 +++++-
src/cargo/core/compiler/build_config.rs | 4 +-
.../compiler/context/compilation_files.rs | 15 ++--
src/cargo/core/compiler/mod.rs | 2 +
src/cargo/core/compiler/rustdoc.rs | 25 ++++++
src/cargo/core/compiler/unit_dependencies.rs | 2 +-
src/cargo/ops/cargo_compile/mod.rs | 2 +-
src/cargo/ops/cargo_doc.rs | 51 +++++++++++-
src/cargo/ops/mod.rs | 2 +-
src/doc/man/cargo-rustdoc.md | 1 +
src/doc/man/generated_txt/cargo-rustdoc.txt | 12 +++
src/doc/man/includes/options-output-format.md | 9 ++
src/doc/src/commands/cargo-rustdoc.md | 9 ++
src/etc/man/cargo-rustdoc.1 | 16 ++++
tests/testsuite/cargo_rustdoc/help/stdout.log | 1 +
tests/testsuite/rustdoc.rs | 82 +++++++++++++++++++
17 files changed, 244 insertions(+), 15 deletions(-)
create mode 100644 src/doc/man/includes/options-output-format.md
diff --git a/src/bin/cargo/commands/doc.rs b/src/bin/cargo/commands/doc.rs
index bf2e0c7babc..de918fb1f24 100644
--- a/src/bin/cargo/commands/doc.rs
+++ b/src/bin/cargo/commands/doc.rs
@@ -49,6 +49,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
let ws = args.workspace(config)?;
let mode = CompileMode::Doc {
deps: !args.flag("no-deps"),
+ json: false,
};
let mut compile_opts =
args.compile_options(config, mode, Some(&ws), ProfileChecking::Custom)?;
@@ -56,6 +57,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
let doc_opts = DocOptions {
open_result: args.flag("open"),
+ output_format: ops::OutputFormat::Html,
compile_opts,
};
ops::doc(&ws, &doc_opts)?;
diff --git a/src/bin/cargo/commands/rustdoc.rs b/src/bin/cargo/commands/rustdoc.rs
index ec4e52c6d72..600247d0e71 100644
--- a/src/bin/cargo/commands/rustdoc.rs
+++ b/src/bin/cargo/commands/rustdoc.rs
@@ -1,4 +1,4 @@
-use cargo::ops::{self, DocOptions};
+use cargo::ops::{self, DocOptions, OutputFormat};
use crate::command_prelude::*;
@@ -38,6 +38,11 @@ pub fn cli() -> Command {
.arg_profile("Build artifacts with the specified profile")
.arg_target_triple("Build for the target triple")
.arg_target_dir()
+ .arg(
+ opt("output-format", "The output type to write (unstable)")
+ .value_name("FMT")
+ .value_parser(OutputFormat::POSSIBLE_VALUES),
+ )
.arg_unit_graph()
.arg_timings()
.arg_manifest_path()
@@ -48,20 +53,35 @@ pub fn cli() -> Command {
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
let ws = args.workspace(config)?;
+ let output_format = if let Some(output_format) = args._value_of("output-format") {
+ config
+ .cli_unstable()
+ .fail_if_stable_opt("--output-format", 12103)?;
+ output_format.parse()?
+ } else {
+ OutputFormat::Html
+ };
+
let mut compile_opts = args.compile_options_for_single_package(
config,
- CompileMode::Doc { deps: false },
+ CompileMode::Doc {
+ deps: false,
+ json: matches!(output_format, OutputFormat::Json),
+ },
Some(&ws),
ProfileChecking::Custom,
)?;
let target_args = values(args, "args");
+
compile_opts.target_rustdoc_args = if target_args.is_empty() {
None
} else {
Some(target_args)
};
+
let doc_opts = DocOptions {
open_result: args.flag("open"),
+ output_format,
compile_opts,
};
ops::doc(&ws, &doc_opts)?;
diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs
index f091df2ad84..78cbb6e614e 100644
--- a/src/cargo/core/compiler/build_config.rs
+++ b/src/cargo/core/compiler/build_config.rs
@@ -176,8 +176,10 @@ pub enum CompileMode {
/// allows some de-duping of Units to occur.
Bench,
/// A target that will be documented with `rustdoc`.
+
/// If `deps` is true, then it will also document all dependencies.
- Doc { deps: bool },
+ /// if `json` is true, the documentation output is in json format.
+ Doc { deps: bool, json: bool },
/// A target that will be tested with `rustdoc`.
Doctest,
/// An example or library that will be scraped for function calls by `rustdoc`.
diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs
index d83dbf10cd1..825044a98a2 100644
--- a/src/cargo/core/compiler/context/compilation_files.rs
+++ b/src/cargo/core/compiler/context/compilation_files.rs
@@ -435,11 +435,16 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
bcx: &BuildContext<'a, 'cfg>,
) -> CargoResult>> {
let ret = match unit.mode {
- CompileMode::Doc { .. } => {
- let path = self
- .out_dir(unit)
- .join(unit.target.crate_name())
- .join("index.html");
+ CompileMode::Doc { json, .. } => {
+ let path = if json {
+ self.out_dir(unit)
+ .join(format!("{}.json", unit.target.crate_name()))
+ } else {
+ self.out_dir(unit)
+ .join(unit.target.crate_name())
+ .join("index.html")
+ };
+
vec![OutputFile {
path,
hardlink: None,
diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs
index 1ac5a6d525c..2f708ce3532 100644
--- a/src/cargo/core/compiler/mod.rs
+++ b/src/cargo/core/compiler/mod.rs
@@ -762,6 +762,8 @@ fn prepare_rustdoc(cx: &Context<'_, '_>, unit: &Unit) -> CargoResult,
+ unit: &Unit,
+ rustdoc: &mut ProcessBuilder,
+) -> CargoResult<()> {
+ let config = cx.bcx.config;
+ if !config.cli_unstable().unstable_options {
+ tracing::debug!("`unstable-options` is ignored, required -Zunstable-options flag");
+ return Ok(());
+ }
+
+ if let CompileMode::Doc { json: true, .. } = unit.mode {
+ rustdoc.arg("-Zunstable-options");
+ rustdoc.arg("--output-format=json");
+ }
+
+ Ok(())
+}
+
/// Indicates whether a target should have examples scraped from it by rustdoc.
/// Configured within Cargo.toml and only for unstable feature
/// [`-Zrustdoc-scrape-examples`][1].
diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs
index 7116ba2070d..e2a237d339e 100644
--- a/src/cargo/core/compiler/unit_dependencies.rs
+++ b/src/cargo/core/compiler/unit_dependencies.rs
@@ -627,7 +627,7 @@ fn compute_deps_doc(
)?;
ret.push(lib_unit_dep);
if dep_lib.documented() {
- if let CompileMode::Doc { deps: true } = unit.mode {
+ if let CompileMode::Doc { deps: true, .. } = unit.mode {
// Document this lib as well.
let doc_unit_dep = new_unit_dep(
state,
diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs
index 3522ef9d34d..a2353ba9923 100644
--- a/src/cargo/ops/cargo_compile/mod.rs
+++ b/src/cargo/ops/cargo_compile/mod.rs
@@ -420,7 +420,7 @@ pub fn create_bcx<'a, 'cfg>(
// TODO: In theory, Cargo should also dedupe the roots, but I'm uncertain
// what heuristics to use in that case.
- if build_config.mode == (CompileMode::Doc { deps: true }) {
+ if matches!(build_config.mode, CompileMode::Doc { deps: true, .. }) {
remove_duplicate_doc(build_config, &units, &mut unit_graph);
}
diff --git a/src/cargo/ops/cargo_doc.rs b/src/cargo/ops/cargo_doc.rs
index ecc17e9fc46..8bdef00d347 100644
--- a/src/cargo/ops/cargo_doc.rs
+++ b/src/cargo/ops/cargo_doc.rs
@@ -2,15 +2,50 @@ use crate::core::{Shell, Workspace};
use crate::ops;
use crate::util::config::{Config, PathAndArgs};
use crate::util::CargoResult;
+use anyhow::{bail, Error};
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
+use std::str::FromStr;
+
+/// Format of rustdoc [`--output-format`][1].
+///
+/// [1]: https://doc.rust-lang.org/nightly/rustdoc/unstable-features.html#-w--output-format-output-format
+#[derive(Debug, Default, Clone)]
+pub enum OutputFormat {
+ #[default]
+ Html,
+ Json,
+}
+
+impl OutputFormat {
+ pub const POSSIBLE_VALUES: [&'static str; 2] = ["html", "json"];
+}
+
+impl FromStr for OutputFormat {
+ // bail! error instead of string error like impl FromStr for Edition {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "json" => Ok(OutputFormat::Json),
+ "html" => Ok(OutputFormat::Html),
+ _ => bail!(
+ "supported values for --output-format are `json` and `html`, \
+ but `{}` is unknown",
+ s
+ ),
+ }
+ }
+}
/// Strongly typed options for the `cargo doc` command.
#[derive(Debug)]
pub struct DocOptions {
/// Whether to attempt to open the browser after compiling the docs
pub open_result: bool,
+ /// Same as `rustdoc --output-format`
+ pub output_format: OutputFormat,
/// Options to pass through to the compiler
pub compile_opts: ops::CompileOptions,
}
@@ -25,10 +60,18 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
.get(0)
.ok_or_else(|| anyhow::anyhow!("no crates with documentation"))?;
let kind = options.compile_opts.build_config.single_requested_kind()?;
- let path = compilation.root_output[&kind]
- .with_file_name("doc")
- .join(&name)
- .join("index.html");
+
+ let path = if matches!(options.output_format, OutputFormat::Json) {
+ compilation.root_output[&kind]
+ .with_file_name("doc")
+ .join(format!("{}.json", &name))
+ } else {
+ compilation.root_output[&kind]
+ .with_file_name("doc")
+ .join(&name)
+ .join("index.html")
+ };
+
if path.exists() {
let config_browser = {
let cfg: Option = ws.config().get("doc.browser")?;
diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs
index 76fa91d2591..09709ce2d00 100644
--- a/src/cargo/ops/mod.rs
+++ b/src/cargo/ops/mod.rs
@@ -5,7 +5,7 @@ pub use self::cargo_compile::{
compile, compile_with_exec, compile_ws, create_bcx, print, resolve_all_features, CompileOptions,
};
pub use self::cargo_compile::{CompileFilter, FilterRule, LibRule, Packages};
-pub use self::cargo_doc::{doc, DocOptions};
+pub use self::cargo_doc::{doc, DocOptions, OutputFormat};
pub use self::cargo_fetch::{fetch, FetchOptions};
pub use self::cargo_generate_lockfile::generate_lockfile;
pub use self::cargo_generate_lockfile::update_lockfile;
diff --git a/src/doc/man/cargo-rustdoc.md b/src/doc/man/cargo-rustdoc.md
index 8379604ac5c..915391a218c 100644
--- a/src/doc/man/cargo-rustdoc.md
+++ b/src/doc/man/cargo-rustdoc.md
@@ -102,6 +102,7 @@ if its name is the same as the lib target. Binaries are skipped if they have
{{#options}}
{{> options-jobs }}
{{> options-keep-going }}
+{{> options-output-format }}
{{/options}}
{{> section-environment }}
diff --git a/src/doc/man/generated_txt/cargo-rustdoc.txt b/src/doc/man/generated_txt/cargo-rustdoc.txt
index 3237c4cae95..e183ef6fe3c 100644
--- a/src/doc/man/generated_txt/cargo-rustdoc.txt
+++ b/src/doc/man/generated_txt/cargo-rustdoc.txt
@@ -327,6 +327,18 @@ OPTIONS
--keep-going would definitely run both builds, even if the one run
first fails.
+ --output-format
+ The output type for the documentation emitted. Valid values:
+
+ o html (default): Emit the documentation in HTML format.
+
+ o json: Emit the documentation in the experimental JSON format
+ .
+
+ This option is only available on the nightly channel
+ and
+ requires the -Z unstable-options flag to enable.
+
ENVIRONMENT
See the reference
diff --git a/src/doc/man/includes/options-output-format.md b/src/doc/man/includes/options-output-format.md
new file mode 100644
index 00000000000..218cacfa072
--- /dev/null
+++ b/src/doc/man/includes/options-output-format.md
@@ -0,0 +1,9 @@
+{{#option "`--output-format`"}}
+The output type for the documentation emitted. Valid values:
+
+* `html` (default): Emit the documentation in HTML format.
+* `json`: Emit the documentation in the [experimental JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types).
+
+This option is only available on the [nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html)
+and requires the `-Z unstable-options` flag to enable.
+{{/option}}
diff --git a/src/doc/src/commands/cargo-rustdoc.md b/src/doc/src/commands/cargo-rustdoc.md
index 79e56c8a879..c81bb549249 100644
--- a/src/doc/src/commands/cargo-rustdoc.md
+++ b/src/doc/src/commands/cargo-rustdoc.md
@@ -371,6 +371,15 @@ one that succeeds (depending on which one of the two builds Cargo picked to run
first), whereas cargo rustdoc -j1 --keep-going
would definitely run both
builds, even if the one run first fails.
+--output-format
+The output type for the documentation emitted. Valid values:
+
+html
(default): Emit the documentation in HTML format.
+json
: Emit the documentation in the experimental JSON format.
+
+This option is only available on the nightly channel
+and requires the -Z unstable-options
flag to enable.
+
## ENVIRONMENT
diff --git a/src/etc/man/cargo-rustdoc.1 b/src/etc/man/cargo-rustdoc.1
index 72a182de8c0..9dff7f12ab5 100644
--- a/src/etc/man/cargo-rustdoc.1
+++ b/src/etc/man/cargo-rustdoc.1
@@ -395,6 +395,22 @@ one that succeeds (depending on which one of the two builds Cargo picked to run
first), whereas \fBcargo rustdoc \-j1 \-\-keep\-going\fR would definitely run both
builds, even if the one run first fails.
.RE
+.sp
+\fB\-\-output\-format\fR
+.RS 4
+The output type for the documentation emitted. Valid values:
+.sp
+.RS 4
+\h'-04'\(bu\h'+02'\fBhtml\fR (default): Emit the documentation in HTML format.
+.RE
+.sp
+.RS 4
+\h'-04'\(bu\h'+02'\fBjson\fR: Emit the documentation in the \fIexperimental JSON format\fR \&.
+.RE
+.sp
+This option is only available on the \fInightly channel\fR
+and requires the \fB\-Z unstable\-options\fR flag to enable.
+.RE
.SH "ENVIRONMENT"
See \fIthe reference\fR for
details on environment variables that Cargo reads.
diff --git a/tests/testsuite/cargo_rustdoc/help/stdout.log b/tests/testsuite/cargo_rustdoc/help/stdout.log
index a0a3cde5d37..6e0e999178e 100644
--- a/tests/testsuite/cargo_rustdoc/help/stdout.log
+++ b/tests/testsuite/cargo_rustdoc/help/stdout.log
@@ -9,6 +9,7 @@ Options:
--open Opens the docs in a browser after the operation
--ignore-rust-version Ignore `rust-version` specification in packages
--message-format Error format
+ --output-format The output type to write (unstable) [possible values: html, json]
-v, --verbose... Use verbose output (-vv very verbose/build.rs output)
-q, --quiet Do not print cargo log messages
--color Coloring: auto, always, never
diff --git a/tests/testsuite/rustdoc.rs b/tests/testsuite/rustdoc.rs
index 7ef768a8044..9b7e092d38f 100644
--- a/tests/testsuite/rustdoc.rs
+++ b/tests/testsuite/rustdoc.rs
@@ -21,6 +21,88 @@ fn rustdoc_simple() {
.run();
}
+#[cargo_test]
+fn rustdoc_simple_html() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc --output-format html --open -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the `--output-format` flag is unstable, and only available on the nightly channel of Cargo, but this is the `stable` channel
+[..]
+See https://github.com/rust-lang/cargo/issues/12103 for more information about the `--output-format` flag.
+",
+ )
+ .run();
+}
+
+#[cargo_test(nightly, reason = "--output-format is unstable")]
+fn rustdoc_simple_json() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc -Z unstable-options --output-format json -v")
+ .masquerade_as_nightly_cargo(&["rustdoc-output-format"])
+ .with_stderr(
+ "\
+[DOCUMENTING] foo v0.0.1 ([CWD])
+[RUNNING] `rustdoc [..]--crate-name foo [..]-o [CWD]/target/doc [..]--output-format=json[..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(p.root().join("target/doc/foo.json").is_file());
+}
+
+#[cargo_test]
+fn rustdoc_invalid_output_format() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc -Z unstable-options --output-format pdf -v")
+ .masquerade_as_nightly_cargo(&["rustdoc-output-format"])
+ .with_status(1)
+ .with_stderr(
+ "\
+error: invalid value 'pdf' for '--output-format '
+ [possible values: html, json]
+
+For more information, try '--help'.
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_json_stable() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc -Z unstable-options --output-format json -v")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the `-Z` flag is only accepted on the nightly channel of Cargo, but this is the `stable` channel
+[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rustdoc_json_without_unstable_options() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("rustdoc --output-format json -v")
+ .masquerade_as_nightly_cargo(&["rustdoc-output-format"])
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the `--output-format` flag is unstable, pass `-Z unstable-options` to enable it
+[..]
+",
+ )
+ .run();
+}
+
#[cargo_test]
fn rustdoc_args() {
let p = project().file("src/lib.rs", "").build();