about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-08-15 00:02:26 +0200
committerGitHub <noreply@github.com>2024-08-15 00:02:26 +0200
commit6c242a0da44215a9c0273802da5148aff32eec2c (patch)
tree58ec5c2e30c85eceb708aa9afd69b5a5f6da7b8c
parentcd1b42c3e9a2f60c3fae428ef839514b7e97eb0e (diff)
parent63ab7b59573a4e8d660b4c0235af6b8789c3753e (diff)
downloadrust-6c242a0da44215a9c0273802da5148aff32eec2c.tar.gz
rust-6c242a0da44215a9c0273802da5148aff32eec2c.zip
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`
-rw-r--r--src/doc/rustdoc/src/unstable-features.md3
-rw-r--r--src/librustdoc/config.rs15
-rw-r--r--src/librustdoc/json/mod.rs47
-rw-r--r--src/tools/run-make-support/src/path_helpers.rs15
-rw-r--r--tests/run-make/rustdoc-output-stdout/foo.rs1
-rw-r--r--tests/run-make/rustdoc-output-stdout/rmake.rs25
6 files changed, 85 insertions, 21 deletions
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 39f24a13143..ebbe141b6f5 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 d599fead266..9e9d8f02a2e 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"),
         };
 
@@ -818,6 +822,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 ea191dc89cf..860672443f2 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<RefCell<FxHashMap<types::Id, types::Item>>>,
     /// The directory where the blob will be written to.
-    out_path: PathBuf,
+    out_path: Option<PathBuf>,
     cache: Rc<Cache>,
     imported_items: DefIdSet,
 }
@@ -97,6 +97,20 @@ impl<'tcx> JsonRenderer<'tcx> {
             })
             .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> {
@@ -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()), "<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 b788bc6ef30..1e6e44c4584 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<P: AsRef<Path>>(path: P, suffix: &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))
 }
+
+/// 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);
+}
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 00000000000..4a835673a59
--- /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 00000000000..e7dfb66602c
--- /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:?}");
+        }
+    });
+}