Skip to content

Commit 6c242a0

Browse files
authored
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`
2 parents cd1b42c + 63ab7b5 commit 6c242a0

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

src/doc/rustdoc/src/unstable-features.md

+3
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@ pub fn no_documentation() {}
515515

516516
Note that the third item is the crate root, which in this case is undocumented.
517517

518+
If you want the JSON output to be displayed on `stdout` instead of having a file generated, you can
519+
use `-o -`.
520+
518521
### `-w`/`--output-format`: output format
519522

520523
`--output-format json` emits documentation in the experimental

src/librustdoc/config.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ pub(crate) struct RenderOptions {
286286
pub(crate) no_emit_shared: bool,
287287
/// If `true`, HTML source code pages won't be generated.
288288
pub(crate) html_no_source: bool,
289+
/// This field is only used for the JSON output. If it's set to true, no file will be created
290+
/// and content will be displayed in stdout directly.
291+
pub(crate) output_to_stdout: bool,
289292
}
290293

291294
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -548,16 +551,17 @@ impl Options {
548551
dcx.fatal("the `--test` flag must be passed to enable `--no-run`");
549552
}
550553

554+
let mut output_to_stdout = false;
551555
let test_builder_wrappers =
552556
matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect();
553-
let out_dir = matches.opt_str("out-dir").map(|s| PathBuf::from(&s));
554-
let output = matches.opt_str("output").map(|s| PathBuf::from(&s));
555-
let output = match (out_dir, output) {
557+
let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) {
556558
(Some(_), Some(_)) => {
557559
dcx.fatal("cannot use both 'out-dir' and 'output' at once");
558560
}
559-
(Some(out_dir), None) => out_dir,
560-
(None, Some(output)) => output,
561+
(Some(out_dir), None) | (None, Some(out_dir)) => {
562+
output_to_stdout = out_dir == "-";
563+
PathBuf::from(out_dir)
564+
}
561565
(None, None) => PathBuf::from("doc"),
562566
};
563567

@@ -818,6 +822,7 @@ impl Options {
818822
call_locations,
819823
no_emit_shared: false,
820824
html_no_source,
825+
output_to_stdout,
821826
};
822827
Some((options, render_options))
823828
}

src/librustdoc/json/mod.rs

+31-16
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod import_finder;
99

1010
use std::cell::RefCell;
1111
use std::fs::{create_dir_all, File};
12-
use std::io::{BufWriter, Write};
12+
use std::io::{stdout, BufWriter, Write};
1313
use std::path::PathBuf;
1414
use std::rc::Rc;
1515

@@ -37,7 +37,7 @@ pub(crate) struct JsonRenderer<'tcx> {
3737
/// level field of the JSON blob.
3838
index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
3939
/// The directory where the blob will be written to.
40-
out_path: PathBuf,
40+
out_path: Option<PathBuf>,
4141
cache: Rc<Cache>,
4242
imported_items: DefIdSet,
4343
}
@@ -97,6 +97,20 @@ impl<'tcx> JsonRenderer<'tcx> {
9797
})
9898
.unwrap_or_default()
9999
}
100+
101+
fn write<T: Write>(
102+
&self,
103+
output: types::Crate,
104+
mut writer: BufWriter<T>,
105+
path: &str,
106+
) -> Result<(), Error> {
107+
self.tcx
108+
.sess
109+
.time("rustdoc_json_serialization", || serde_json::ser::to_writer(&mut writer, &output))
110+
.unwrap();
111+
try_err!(writer.flush(), path);
112+
Ok(())
113+
}
100114
}
101115

102116
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
@@ -120,7 +134,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
120134
JsonRenderer {
121135
tcx,
122136
index: Rc::new(RefCell::new(FxHashMap::default())),
123-
out_path: options.output,
137+
out_path: if options.output_to_stdout { None } else { Some(options.output) },
124138
cache: Rc::new(cache),
125139
imported_items,
126140
},
@@ -264,20 +278,21 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
264278
.collect(),
265279
format_version: types::FORMAT_VERSION,
266280
};
267-
let out_dir = self.out_path.clone();
268-
try_err!(create_dir_all(&out_dir), out_dir);
281+
if let Some(ref out_path) = self.out_path {
282+
let out_dir = out_path.clone();
283+
try_err!(create_dir_all(&out_dir), out_dir);
269284

270-
let mut p = out_dir;
271-
p.push(output.index.get(&output.root).unwrap().name.clone().unwrap());
272-
p.set_extension("json");
273-
let mut file = BufWriter::new(try_err!(File::create(&p), p));
274-
self.tcx
275-
.sess
276-
.time("rustdoc_json_serialization", || serde_json::ser::to_writer(&mut file, &output))
277-
.unwrap();
278-
try_err!(file.flush(), p);
279-
280-
Ok(())
285+
let mut p = out_dir;
286+
p.push(output.index.get(&output.root).unwrap().name.clone().unwrap());
287+
p.set_extension("json");
288+
self.write(
289+
output,
290+
BufWriter::new(try_err!(File::create(&p), p)),
291+
&p.display().to_string(),
292+
)
293+
} else {
294+
self.write(output, BufWriter::new(stdout()), "<stdout>")
295+
}
281296
}
282297

283298
fn cache(&self) -> &Cache {

src/tools/run-make-support/src/path_helpers.rs

+15
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,18 @@ pub fn has_suffix<P: AsRef<Path>>(path: P, suffix: &str) -> bool {
8484
pub fn filename_contains<P: AsRef<Path>>(path: P, needle: &str) -> bool {
8585
path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(needle))
8686
}
87+
88+
/// Helper for reading entries in a given directory and its children.
89+
pub fn read_dir_entries_recursive<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, mut callback: F) {
90+
fn read_dir_entries_recursive_inner<P: AsRef<Path>, F: FnMut(&Path)>(dir: P, callback: &mut F) {
91+
for entry in rfs::read_dir(dir) {
92+
let path = entry.unwrap().path();
93+
callback(&path);
94+
if path.is_dir() {
95+
read_dir_entries_recursive_inner(path, callback);
96+
}
97+
}
98+
}
99+
100+
read_dir_entries_recursive_inner(dir, &mut callback);
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub struct Foo;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// This test verifies that rustdoc `-o -` prints JSON on stdout and doesn't generate
2+
// a JSON file.
3+
4+
use std::path::PathBuf;
5+
6+
use run_make_support::path_helpers::{cwd, has_extension, read_dir_entries_recursive};
7+
use run_make_support::rustdoc;
8+
9+
fn main() {
10+
// First we check that we generate the JSON in the stdout.
11+
rustdoc()
12+
.input("foo.rs")
13+
.output("-")
14+
.arg("-Zunstable-options")
15+
.output_format("json")
16+
.run()
17+
.assert_stdout_contains("{\"");
18+
19+
// Then we check it didn't generate any JSON file.
20+
read_dir_entries_recursive(cwd(), |path| {
21+
if path.is_file() && has_extension(path, "json") {
22+
panic!("Found a JSON file {path:?}");
23+
}
24+
});
25+
}

0 commit comments

Comments
 (0)