From 4441edf3de910d4f4a4b013626c16d7cc33120c5 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 | 26 +++++- 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 | 3 +- tests/testsuite/rustdoc.rs | 82 +++++++++++++++++++ 17 files changed, 247 insertions(+), 16 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 bf2e0c7babcb..de918fb1f243 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 ec4e52c6d723..54e699afff44 100644 --- a/src/bin/cargo/commands/rustdoc.rs +++ b/src/bin/cargo/commands/rustdoc.rs @@ -1,4 +1,6 @@ -use cargo::ops::{self, DocOptions}; +use std::str::FromStr; + +use cargo::ops::{self, DocOptions, OutputFormat}; use crate::command_prelude::*; @@ -38,6 +40,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 +55,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)?; + OutputFormat::from_str(output_format).unwrap() + } 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 f091df2ad844..78cbb6e614e1 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 d83dbf10cd18..825044a98a2e 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 1ac5a6d525cd..2f708ce3532e 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 7116ba2070dd..e2a237d339ec 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 3522ef9d34dd..a2353ba99231 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 ecc17e9fc463..8bdef00d347e 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 76fa91d25918..09709ce2d001 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 8379604ac5c2..915391a218ca 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 3237c4cae950..e183ef6fe3c7 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 000000000000..218cacfa0723 --- /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 79e56c8a8790..c81bb5492492 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 72a182de8c02..9dff7f12ab51 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 a0a3cde5d37c..5b0b4c519fb9 100644 --- a/tests/testsuite/cargo_rustdoc/help/stdout.log +++ b/tests/testsuite/cargo_rustdoc/help/stdout.log @@ -1,6 +1,6 @@ Build a package's documentation, using specified custom flags. -Usage: cargo[EXE] rustdoc [OPTIONS] [ARGS]... +Usage: cargo rustdoc [OPTIONS] [ARGS]... Arguments: [ARGS]... Extra rustdoc flags @@ -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 7ef768a8044b..4d87b4effc52 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(nightly, reason = "--output-format is unstable")] +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(nightly, reason = "--output-format is unstable")] +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();