diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 39f24a13143c8..ebbe141b6f540 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -515,6 +515,9 @@ pub fn no_documentation() {} 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 `--output-format json` emits documentation in the experimental diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 9c7a9f8467f5a..cd2892e7a252d 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -286,6 +286,9 @@ pub(crate) struct RenderOptions { pub(crate) no_emit_shared: bool, /// If `true`, HTML source code pages won't be generated. 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)] @@ -548,16 +551,17 @@ impl Options { dcx.fatal("the `--test` flag must be passed to enable `--no-run`"); } + let mut output_to_stdout = false; let test_builder_wrappers = 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 = matches.opt_str("output").map(|s| PathBuf::from(&s)); - let output = match (out_dir, output) { + let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) { (Some(_), Some(_)) => { dcx.fatal("cannot use both 'out-dir' and 'output' at once"); } - (Some(out_dir), None) => out_dir, - (None, Some(output)) => output, + (Some(out_dir), None) | (None, Some(out_dir)) => { + output_to_stdout = out_dir == "-"; + PathBuf::from(out_dir) + } (None, None) => PathBuf::from("doc"), }; @@ -816,6 +820,7 @@ impl Options { call_locations, no_emit_shared: false, html_no_source, + output_to_stdout, }; Some((options, render_options)) } diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index ea191dc89cfde..860672443f2e8 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -9,7 +9,7 @@ mod import_finder; use std::cell::RefCell; use std::fs::{create_dir_all, File}; -use std::io::{BufWriter, Write}; +use std::io::{stdout, BufWriter, Write}; use std::path::PathBuf; use std::rc::Rc; @@ -37,7 +37,7 @@ pub(crate) struct JsonRenderer<'tcx> { /// level field of the JSON blob. index: Rc>>, /// The directory where the blob will be written to. - out_path: PathBuf, + out_path: Option, cache: Rc, imported_items: DefIdSet, } @@ -97,6 +97,20 @@ impl<'tcx> JsonRenderer<'tcx> { }) .unwrap_or_default() } + + fn write( + &self, + output: types::Crate, + mut writer: BufWriter, + 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> { @@ -120,7 +134,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { JsonRenderer { tcx, 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), imported_items, }, @@ -264,20 +278,21 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { .collect(), format_version: types::FORMAT_VERSION, }; - let out_dir = self.out_path.clone(); - try_err!(create_dir_all(&out_dir), out_dir); + if let Some(ref out_path) = self.out_path { + let out_dir = out_path.clone(); + try_err!(create_dir_all(&out_dir), out_dir); - let mut p = out_dir; - p.push(output.index.get(&output.root).unwrap().name.clone().unwrap()); - p.set_extension("json"); - let mut file = BufWriter::new(try_err!(File::create(&p), p)); - self.tcx - .sess - .time("rustdoc_json_serialization", || serde_json::ser::to_writer(&mut file, &output)) - .unwrap(); - try_err!(file.flush(), p); - - Ok(()) + let mut p = out_dir; + p.push(output.index.get(&output.root).unwrap().name.clone().unwrap()); + p.set_extension("json"); + self.write( + output, + BufWriter::new(try_err!(File::create(&p), p)), + &p.display().to_string(), + ) + } else { + self.write(output, BufWriter::new(stdout()), "") + } } fn cache(&self) -> &Cache { diff --git a/src/tools/run-make-support/src/path_helpers.rs b/src/tools/run-make-support/src/path_helpers.rs index b788bc6ef302c..1e6e44c458423 100644 --- a/src/tools/run-make-support/src/path_helpers.rs +++ b/src/tools/run-make-support/src/path_helpers.rs @@ -84,3 +84,18 @@ pub fn has_suffix>(path: P, suffix: &str) -> bool { pub fn filename_contains>(path: P, needle: &str) -> bool { 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, F: FnMut(&Path)>(dir: P, mut callback: F) { + fn read_dir_entries_recursive_inner, 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); +} diff --git a/tests/run-make/rustdoc-output-stdout/foo.rs b/tests/run-make/rustdoc-output-stdout/foo.rs new file mode 100644 index 0000000000000..4a835673a596b --- /dev/null +++ b/tests/run-make/rustdoc-output-stdout/foo.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/run-make/rustdoc-output-stdout/rmake.rs b/tests/run-make/rustdoc-output-stdout/rmake.rs new file mode 100644 index 0000000000000..e7dfb66602cf3 --- /dev/null +++ b/tests/run-make/rustdoc-output-stdout/rmake.rs @@ -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:?}"); + } + }); +}