Skip to content

rustdoc-json: Postcard output #142642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,12 @@ dependencies = [
"serde",
]

[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"

[[package]]
name = "collect-license-metadata"
version = "0.1.0"
Expand Down Expand Up @@ -1096,6 +1102,18 @@ dependencies = [
"stable_deref_trait",
]

[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"

[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"

[[package]]
name = "ena"
version = "0.14.3"
Expand Down Expand Up @@ -1992,8 +2010,10 @@ name = "jsondoclint"
version = "0.1.0"
dependencies = [
"anyhow",
"camino",
"clap",
"fs-err",
"postcard",
"rustc-hash 2.1.1",
"rustdoc-json-types",
"serde",
Expand Down Expand Up @@ -2821,6 +2841,18 @@ dependencies = [
"portable-atomic",
]

[[package]]
name = "postcard"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"serde",
]

[[package]]
name = "potential_utf"
version = "0.1.2"
Expand Down Expand Up @@ -4661,6 +4693,7 @@ dependencies = [
"indexmap",
"itertools",
"minifier",
"postcard",
"pulldown-cmark-escape",
"regex",
"rustdoc-json-types",
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tracing = "0.1"
tracing-tree = "0.3.0"
threadpool = "1.8.1"
unicode-segmentation = "1.9"
postcard = { version = "1.1.1", default-features = false, features = ["use-std"] }

[dependencies.tracing-subscriber]
version = "0.3.3"
Expand Down
9 changes: 7 additions & 2 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ use crate::{html, opts, theme};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) enum OutputFormat {
Json,
Postcard,
#[default]
Html,
Doctest,
}

impl OutputFormat {
pub(crate) fn is_json(&self) -> bool {
matches!(self, OutputFormat::Json)
matches!(self, OutputFormat::Json | OutputFormat::Postcard)
}
}

Expand All @@ -50,6 +51,7 @@ impl TryFrom<&str> for OutputFormat {
"json" => Ok(OutputFormat::Json),
"html" => Ok(OutputFormat::Html),
"doctest" => Ok(OutputFormat::Doctest),
"postcard" => Ok(OutputFormat::Postcard),
_ => Err(format!("unknown output format `{value}`")),
}
}
Expand Down Expand Up @@ -305,6 +307,8 @@ pub(crate) struct RenderOptions {
pub(crate) parts_out_dir: Option<PathToParts>,
/// disable minification of CSS/JS
pub(crate) disable_minification: bool,

pub(crate) output_format: OutputFormat,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -488,7 +492,7 @@ impl Options {
// If `-Zunstable-options` is used, nothing to check after this point.
(_, false, true) => {}
(None | Some(OutputFormat::Html), false, _) => {}
(Some(OutputFormat::Json), false, false) => {
(Some(OutputFormat::Json | OutputFormat::Postcard), false, false) => {
dcx.fatal(
"the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)",
);
Expand Down Expand Up @@ -886,6 +890,7 @@ impl Options {
include_parts_dir,
parts_out_dir,
disable_minification,
output_format,
};
Some((input, options, render_options))
}
Expand Down
47 changes: 42 additions & 5 deletions src/librustdoc/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ use crate::formats::cache::Cache;
use crate::json::conversions::IntoJson;
use crate::{clean, try_err};

#[derive(Clone, Copy, Debug)]
pub(crate) enum OutputFormat {
Json,
Postcard,
}

pub(crate) struct JsonRenderer<'tcx> {
tcx: TyCtxt<'tcx>,
/// A mapping of IDs that contains all local items for this crate which gets output as a top
Expand All @@ -49,6 +55,7 @@ pub(crate) struct JsonRenderer<'tcx> {
cache: Rc<Cache>,
imported_items: DefIdSet,
id_interner: RefCell<ids::IdInterner>,
output_format: OutputFormat,
}

impl<'tcx> JsonRenderer<'tcx> {
Expand Down Expand Up @@ -114,10 +121,30 @@ impl<'tcx> JsonRenderer<'tcx> {
path: &str,
) -> Result<(), Error> {
self.sess().time("rustdoc_json_serialize_and_write", || {
try_err!(
serde_json::ser::to_writer(&mut writer, &output_crate).map_err(|e| e.to_string()),
path
);
match self.output_format {
OutputFormat::Json => {
try_err!(
serde_json::ser::to_writer(&mut writer, &output_crate)
.map_err(|e| e.to_string()),
path
);
}
OutputFormat::Postcard => {
let output = (
rustdoc_json_types::postcard::Header {
magic: rustdoc_json_types::postcard::MAGIC,
format_version: rustdoc_json_types::FORMAT_VERSION,
},
output_crate,
);

try_err!(
postcard::to_io(&output, &mut writer).map_err(|e| e.to_string()),
path
);
}
}

try_err!(writer.flush(), path);
Ok(())
})
Expand Down Expand Up @@ -201,6 +228,13 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
out_dir: if options.output_to_stdout { None } else { Some(options.output) },
cache: Rc::new(cache),
imported_items,
output_format: match options.output_format {
crate::config::OutputFormat::Json => OutputFormat::Json,
crate::config::OutputFormat::Postcard => OutputFormat::Postcard,
crate::config::OutputFormat::Html | crate::config::OutputFormat::Doctest => {
unreachable!()
}
},
id_interner: Default::default(),
},
krate,
Expand Down Expand Up @@ -366,7 +400,10 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {

let mut p = out_dir.clone();
p.push(output_crate.index.get(&output_crate.root).unwrap().name.clone().unwrap());
p.set_extension("json");
p.set_extension(match self.output_format {
OutputFormat::Json => "json",
OutputFormat::Postcard => "postcard",
});

self.serialize_and_write(
output_crate,
Expand Down
7 changes: 4 additions & 3 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -922,9 +922,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
config::OutputFormat::Html => sess.time("render_html", || {
run_renderer::<html::render::Context<'_>>(krate, render_opts, cache, tcx)
}),
config::OutputFormat::Json => sess.time("render_json", || {
run_renderer::<json::JsonRenderer<'_>>(krate, render_opts, cache, tcx)
}),
config::OutputFormat::Json | config::OutputFormat::Postcard => sess
.time("render_json", || {
run_renderer::<json::JsonRenderer<'_>>(krate, render_opts, cache, tcx)
}),
// Already handled above with doctest runners.
config::OutputFormat::Doctest => unreachable!(),
}
Expand Down
13 changes: 13 additions & 0 deletions src/rustdoc-json-types/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
/// Consuming code should assert that this value matches the format version(s) that it supports.
pub const FORMAT_VERSION: u32 = 46;

pub mod postcard {

pub type Magic = [u8; 22];
pub const MAGIC: Magic = *b"\x00\xFFRustdocJsonPostcard\xFF";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A friend points out https://hackers.town/@zwol/114155807716413069, with advice on how to design a magic number.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having 'Json' in there seems perverse :)


#[derive(Clone, Debug, PartialEq, Eq, serde_derive::Serialize, serde_derive::Deserialize)]
pub struct Header {
// Order here matters
pub magic: Magic,
pub format_version: u32,
}
}

/// The root of the emitted JSON blob.
///
/// It contains all type/documentation information
Expand Down
8 changes: 8 additions & 0 deletions src/rustdoc-json-types/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ fn test_union_info_roundtrip() {
let decoded: ItemEnum = bincode::deserialize(&encoded).unwrap();
assert_eq!(u, decoded);
}

#[test]
fn magic_never_dies() {
// Extra check to make sure that the postcard magic header never changes.
// Don't change this value
assert_eq!(crate::postcard::MAGIC, *b"\x00\xFFRustdocJsonPostcard\xFF");
// Don't change that value
}
30 changes: 25 additions & 5 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ enum Emit {
LinkArgsAsm,
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum DocKind {
Html,
Json,
Postcard,
}

impl<'test> TestCx<'test> {
/// Code executed for each revision in turn (or, if there are no
/// revisions, exactly once, with revision == None).
Expand Down Expand Up @@ -861,8 +868,15 @@ impl<'test> TestCx<'test> {

/// `root_out_dir` and `root_testpaths` refer to the parameters of the actual test being run.
/// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths.
fn document(&self, root_out_dir: &Utf8Path, root_testpaths: &TestPaths) -> ProcRes {
fn document(
&self,
root_out_dir: &Utf8Path,
root_testpaths: &TestPaths,
kind: DocKind,
) -> ProcRes {
if self.props.build_aux_docs {
assert_eq!(kind, DocKind::Html, "build-aux-docs doesn't make sense for rustdoc json");

for rel_ab in &self.props.aux.builds {
let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
let props_for_aux =
Expand All @@ -877,7 +891,7 @@ impl<'test> TestCx<'test> {
create_dir_all(aux_cx.output_base_dir()).unwrap();
// use root_testpaths here, because aux-builds should have the
// same --out-dir and auxiliary directory.
let auxres = aux_cx.document(&root_out_dir, root_testpaths);
let auxres = aux_cx.document(&root_out_dir, root_testpaths, kind);
if !auxres.status.success() {
return auxres;
}
Expand Down Expand Up @@ -922,8 +936,14 @@ impl<'test> TestCx<'test> {
.args(&self.props.compile_flags)
.args(&self.props.doc_flags);

if self.config.mode == RustdocJson {
rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
match kind {
DocKind::Html => {}
DocKind::Json => {
rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
}
DocKind::Postcard => {
rustdoc.arg("--output-format").arg("postcard").arg("-Zunstable-options");
}
}

if let Some(ref linker) = self.config.target_linker {
Expand Down Expand Up @@ -1992,7 +2012,7 @@ impl<'test> TestCx<'test> {
let aux_dir = new_rustdoc.aux_output_dir();
new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);

let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths, DocKind::Html);
if !proc_res.status.success() {
eprintln!("failed to run nightly rustdoc");
return;
Expand Down
4 changes: 2 additions & 2 deletions src/tools/compiletest/src/runtest/js_doc.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::process::Command;

use super::TestCx;
use super::{DocKind, TestCx};

impl TestCx<'_> {
pub(super) fn run_rustdoc_js_test(&self) {
if let Some(nodejs) = &self.config.nodejs {
let out_dir = self.output_base_dir();

self.document(&out_dir, &self.testpaths);
self.document(&out_dir, &self.testpaths, DocKind::Html);

let file_stem = self.testpaths.file.file_stem().expect("no file stem");
let res = self.run_command_to_procres(
Expand Down
3 changes: 2 additions & 1 deletion src/tools/compiletest/src/runtest/rustdoc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::process::Command;

use super::{TestCx, remove_and_create_dir_all};
use crate::runtest::DocKind;

impl TestCx<'_> {
pub(super) fn run_rustdoc_test(&self) {
Expand All @@ -11,7 +12,7 @@ impl TestCx<'_> {
panic!("failed to remove and recreate output directory `{out_dir}`: {e}")
});

let proc_res = self.document(&out_dir, &self.testpaths);
let proc_res = self.document(&out_dir, &self.testpaths, DocKind::Html);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
Expand Down
13 changes: 9 additions & 4 deletions src/tools/compiletest/src/runtest/rustdoc_json.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::process::Command;

use super::{TestCx, remove_and_create_dir_all};
use crate::runtest::DocKind;

impl TestCx<'_> {
pub(super) fn run_rustdoc_json_test(&self) {
Expand All @@ -13,7 +14,11 @@ impl TestCx<'_> {
panic!("failed to remove and recreate output directory `{out_dir}`: {e}")
});

let proc_res = self.document(&out_dir, &self.testpaths);
let proc_res = self.document(&out_dir, &self.testpaths, DocKind::Json);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
let proc_res = self.document(&out_dir, &self.testpaths, DocKind::Postcard);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
Expand All @@ -35,11 +40,11 @@ impl TestCx<'_> {
})
}

let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap());
json_out.set_extension("json");
let postcard_out = json_out.with_extension("postcard");

let res = self.run_command_to_procres(
Command::new(self.config.jsondoclint_path.as_ref().unwrap()).arg(&json_out),
Command::new(self.config.jsondoclint_path.as_ref().unwrap())
.args([&json_out, &postcard_out]),
);

if !res.status.success() {
Expand Down
Loading
Loading