1
Fork 0

Rollup merge of #128963 - GuillaumeGomez:output-to-stdout, r=aDotInTheVoid

Add possibility to generate rustdoc JSON output to stdout

Fixes #127165.

I think it's likely common to want to get rustdoc json output directly instead of reading it from a file so I added this option to allow it. It's unstable and only works with `--output-format=json`.

r? `@aDotInTheVoid`
This commit is contained in:
Matthias Krüger 2024-08-15 00:02:26 +02:00 committed by GitHub
commit 6c242a0da4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 85 additions and 21 deletions

View file

@ -515,6 +515,9 @@ pub fn no_documentation() {}
Note that the third item is the crate root, which in this case is undocumented. Note that the third item is the crate root, which in this case is undocumented.
If you want the JSON output to be displayed on `stdout` instead of having a file generated, you can
use `-o -`.
### `-w`/`--output-format`: output format ### `-w`/`--output-format`: output format
`--output-format json` emits documentation in the experimental `--output-format json` emits documentation in the experimental

View file

@ -286,6 +286,9 @@ pub(crate) struct RenderOptions {
pub(crate) no_emit_shared: bool, pub(crate) no_emit_shared: bool,
/// If `true`, HTML source code pages won't be generated. /// If `true`, HTML source code pages won't be generated.
pub(crate) html_no_source: bool, pub(crate) html_no_source: bool,
/// This field is only used for the JSON output. If it's set to true, no file will be created
/// and content will be displayed in stdout directly.
pub(crate) output_to_stdout: bool,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -548,16 +551,17 @@ impl Options {
dcx.fatal("the `--test` flag must be passed to enable `--no-run`"); dcx.fatal("the `--test` flag must be passed to enable `--no-run`");
} }
let mut output_to_stdout = false;
let test_builder_wrappers = let test_builder_wrappers =
matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect(); matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect();
let out_dir = matches.opt_str("out-dir").map(|s| PathBuf::from(&s)); let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) {
let output = matches.opt_str("output").map(|s| PathBuf::from(&s));
let output = match (out_dir, output) {
(Some(_), Some(_)) => { (Some(_), Some(_)) => {
dcx.fatal("cannot use both 'out-dir' and 'output' at once"); dcx.fatal("cannot use both 'out-dir' and 'output' at once");
} }
(Some(out_dir), None) => out_dir, (Some(out_dir), None) | (None, Some(out_dir)) => {
(None, Some(output)) => output, output_to_stdout = out_dir == "-";
PathBuf::from(out_dir)
}
(None, None) => PathBuf::from("doc"), (None, None) => PathBuf::from("doc"),
}; };
@ -818,6 +822,7 @@ impl Options {
call_locations, call_locations,
no_emit_shared: false, no_emit_shared: false,
html_no_source, html_no_source,
output_to_stdout,
}; };
Some((options, render_options)) Some((options, render_options))
} }

View file

@ -9,7 +9,7 @@ mod import_finder;
use std::cell::RefCell; use std::cell::RefCell;
use std::fs::{create_dir_all, File}; use std::fs::{create_dir_all, File};
use std::io::{BufWriter, Write}; use std::io::{stdout, BufWriter, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
@ -37,7 +37,7 @@ pub(crate) struct JsonRenderer<'tcx> {
/// level field of the JSON blob. /// level field of the JSON blob.
index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>, index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
/// The directory where the blob will be written to. /// The directory where the blob will be written to.
out_path: PathBuf, out_path: Option<PathBuf>,
cache: Rc<Cache>, cache: Rc<Cache>,
imported_items: DefIdSet, imported_items: DefIdSet,
} }
@ -97,6 +97,20 @@ impl<'tcx> JsonRenderer<'tcx> {
}) })
.unwrap_or_default() .unwrap_or_default()
} }
fn write<T: Write>(
&self,
output: types::Crate,
mut writer: BufWriter<T>,
path: &str,
) -> Result<(), Error> {
self.tcx
.sess
.time("rustdoc_json_serialization", || serde_json::ser::to_writer(&mut writer, &output))
.unwrap();
try_err!(writer.flush(), path);
Ok(())
}
} }
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
@ -120,7 +134,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
JsonRenderer { JsonRenderer {
tcx, tcx,
index: Rc::new(RefCell::new(FxHashMap::default())), index: Rc::new(RefCell::new(FxHashMap::default())),
out_path: options.output, out_path: if options.output_to_stdout { None } else { Some(options.output) },
cache: Rc::new(cache), cache: Rc::new(cache),
imported_items, imported_items,
}, },
@ -264,20 +278,21 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
.collect(), .collect(),
format_version: types::FORMAT_VERSION, format_version: types::FORMAT_VERSION,
}; };
let out_dir = self.out_path.clone(); if let Some(ref out_path) = self.out_path {
let out_dir = out_path.clone();
try_err!(create_dir_all(&out_dir), out_dir); try_err!(create_dir_all(&out_dir), out_dir);
let mut p = out_dir; let mut p = out_dir;
p.push(output.index.get(&output.root).unwrap().name.clone().unwrap()); p.push(output.index.get(&output.root).unwrap().name.clone().unwrap());
p.set_extension("json"); p.set_extension("json");
let mut file = BufWriter::new(try_err!(File::create(&p), p)); self.write(
self.tcx output,
.sess BufWriter::new(try_err!(File::create(&p), p)),
.time("rustdoc_json_serialization", || serde_json::ser::to_writer(&mut file, &output)) &p.display().to_string(),
.unwrap(); )
try_err!(file.flush(), p); } else {
self.write(output, BufWriter::new(stdout()), "<stdout>")
Ok(()) }
} }
fn cache(&self) -> &Cache { fn cache(&self) -> &Cache {

View file

@ -84,3 +84,18 @@ pub fn has_suffix<P: AsRef<Path>>(path: P, suffix: &str) -> bool {
pub fn filename_contains<P: AsRef<Path>>(path: P, needle: &str) -> bool { pub fn filename_contains<P: AsRef<Path>>(path: P, needle: &str) -> bool {
path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(needle)) path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(needle))
} }
/// Helper for reading entries in a given directory and its children.
pub fn read_dir_entries_recursive<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
fn read_dir_entries_recursive_inner<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, callback: &mut F) {
for entry in rfs::read_dir(dir) {
let path = entry.unwrap().path();
callback(&path);
if path.is_dir() {
read_dir_entries_recursive_inner(path, callback);
}
}
}
read_dir_entries_recursive_inner(dir, &mut callback);
}

View file

@ -0,0 +1 @@
pub struct Foo;

View file

@ -0,0 +1,25 @@
// This test verifies that rustdoc `-o -` prints JSON on stdout and doesn't generate
// a JSON file.
use std::path::PathBuf;
use run_make_support::path_helpers::{cwd, has_extension, read_dir_entries_recursive};
use run_make_support::rustdoc;
fn main() {
// First we check that we generate the JSON in the stdout.
rustdoc()
.input("foo.rs")
.output("-")
.arg("-Zunstable-options")
.output_format("json")
.run()
.assert_stdout_contains("{\"");
// Then we check it didn't generate any JSON file.
read_dir_entries_recursive(cwd(), |path| {
if path.is_file() && has_extension(path, "json") {
panic!("Found a JSON file {path:?}");
}
});
}