Skip to content
Open
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
23 changes: 12 additions & 11 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,15 @@ This is very easy to use in scripts that manually invoke rustdoc, but it's also
performs O(crates) work on every crate, meaning it performs O(crates<sup>2</sup>) work. When
`--write-doc-meta-dir` and/or `--read-doc-meta-dir` are supplied, this is turned off.

When `--write-doc-meta-dir` is supplied, rustdoc will write the crate's metadata to that directory.
If this parameter is supplied but `--read-doc-meta-dir` isn't, it runs in *intermediate mode*:
some pages may be written to the output dir, but there is a lot of functionality that won't work
until rustdoc is run in *finalize mode*.
When `--write-doc-meta-dir` is supplied, rustdoc will write the crate's shared metadata to
that directory. This is an *intermediate mode* where it may write some files to the doc output
directory, but some features won't work until it is finalized.

When `--read-doc-meta-dir` is supplied, rustdoc runs in *finalize mode*. It will read the data from
the supplied directory, and will write it to the doc output directory in the form that the web
frontend will use.
When `--read-doc-meta-dir` is supplied, it runs it in *finalize mode*. No crate source code is

@camelid camelid Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
When `--read-doc-meta-dir` is supplied, it runs it in *finalize mode*. No crate source code is
When `--read-doc-meta-dir` is supplied, rustdoc runs in *finalize mode*. No crate source code is

View changes since the review

passed to rustdoc when it runs in this mode. Multiple `--read-doc-meta-dir` can be passed to
rustdoc, so your build system can split the state between multiple directories if that helps.

If both `--write-doc-meta-dir` and `--read-doc-meta-dir` are specified, the crate metadata will be
written to both the HTML `--out-dir` and to the supplied `--write-doc-meta-dir`.
Both `--write-doc-meta-dir` and `--read-doc-meta-dir` can't be supplied at once.

@camelid camelid Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Both `--write-doc-meta-dir` and `--read-doc-meta-dir` can't be supplied at once.
`--write-doc-meta-dir` and `--read-doc-meta-dir` cannot both be passed to the same rustdoc invocation.

View changes since the review


```console
$ rustdoc crate1.rs --out-dir=doc
Expand All @@ -231,10 +229,13 @@ To delay shared-data merging until the end of a build, so that you only have to
work, use `--write-doc-meta-dir` on every crate, and the last will use `--read-doc-meta-dir`.

```console
$ rustdoc +nightly crate1.rs --write-doc-meta=crate1.d -Zunstable-options
$ rustdoc +nightly crate1.rs --write-doc-meta-dir=crate1.d -Zunstable-options
$ cat doc/search.index/crateNames/*
cat: 'doc/search.index/crateNames/*': No such file or directory
$ rustdoc +nightly crate2.rs --read-doc-meta=crate1.d -Zunstable-options
$ rustdoc +nightly crate2.rs --write-doc-meta-dir=crate2.d -Zunstable-options
$ cat doc/search.index/crateNames/*
cat: 'doc/search.index/crateNames/*': No such file or directory
$ rustdoc +nightly --read-doc-meta-dir=crate1.d --read-doc-meta-dir=crate2.d -Zunstable-options
$ cat doc/search.index/crateNames/*
rd_("fcrate1fcrate2")
```
Expand Down
58 changes: 32 additions & 26 deletions src/librustdoc/html/render/write_shared.rs

@camelid camelid Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't fully understand the purpose of the changes in this commit. Did --enable-index-page not work at all when CCI was enabled, prior to this change? Could you explain a bit?

View changes since the review

Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,9 @@ pub(crate) fn write_shared(
cx.shared.layout.css_file_extension.as_deref(),
&cx.shared.resource_suffix,
cx.info.include_sources,
&cx.shared.layout,
cx.shared.tcx,
)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
md_opts.output = cx.dst.clone();
md_opts.external_html = cx.shared.layout.external_html.clone();
let file = try_err!(cx.sess().source_map().load_file(&index_page), &index_page);
try_err!(
crate::markdown::render_and_write(file, md_opts, cx.shared.edition()),
&index_page
);
}
None if opt.enable_index_page => {
write_rendered_cci::<CratesIndexPart, _>(
|| CratesIndexPart::blank(cx),
&cx.dst,
&crates,
&opt.should_merge,
)?;
}
_ => {} // they don't want an index page
}
}

cx.shared.fs.set_sync_only(false);
Expand All @@ -150,9 +131,32 @@ pub(crate) fn write_not_crate_specific(
css_file_extension: Option<&Path>,
resource_suffix: &str,
include_sources: bool,
layout: &layout::Layout,
tcx: TyCtxt<'_>,
) -> Result<(), Error> {
write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
write_resources(dst, opt, style_files, css_file_extension, resource_suffix)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
md_opts.output = dst.to_path_buf();
md_opts.external_html = layout.external_html.clone();
let file = try_err!(tcx.sess.source_map().load_file(&index_page), &index_page);
try_err!(
crate::markdown::render_and_write(file, md_opts, tcx.sess.edition()),
&index_page
);
}
None if opt.enable_index_page => {
write_rendered_cci::<CratesIndexPart, _>(
|| CratesIndexPart::blank(layout, opt, style_files),
&dst,
&crates,
&opt.should_merge,
)?;
}
_ => {} // they don't want an index page
}
Ok(())
}

Expand Down Expand Up @@ -399,19 +403,21 @@ impl CciPart for CratesIndexPart {
}

impl CratesIndexPart {
fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
fn blank(
layout: &layout::Layout,
opt: &RenderOptions,
style_files: &[StylePath],
) -> SortedTemplate<<Self as CciPart>::FileFormat> {
let page = layout::Page {
title: "Index of crates",
short_title: "Crates",
css_class: "mod sys",
root_path: "./",
static_root_path: cx.shared.static_root_path.as_deref(),
static_root_path: opt.static_root_path.as_deref(),
description: "List of crates",
resource_suffix: &cx.shared.resource_suffix,
resource_suffix: &opt.resource_suffix,
rust_logo: true,
};
let layout = &cx.shared.layout;
let style_files = &cx.shared.style_files;
const DELIMITER: &str = "\u{FFFC}"; // users are being naughty if they have this
let content = format_args!(
"<div class=\"main-heading\">\
Expand Down
60 changes: 46 additions & 14 deletions src/librustdoc/lib.rs

@camelid camelid Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where is the check that errors when both --read... and --write... are passed?

View changes since the review

Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ use rustc_errors::DiagCtxtHandle;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_interface::interface;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::{ErrorOutputType, RustcOptGroup, make_crate_type_option};
use rustc_session::config::{ErrorOutputType, Input, RustcOptGroup, make_crate_type_option};
use rustc_session::{EarlyDiagCtxt, getopts};
use rustc_span::{BytePos, Span, SyntaxContext};
use tracing::info;
Expand Down Expand Up @@ -772,7 +772,10 @@ fn run_renderer<
/// Renders and writes cross-crate info files, like the search index. This function exists so that
/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
/// discovered via `--read-doc-meta-dir` are combined and written to the doc root.
fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
fn run_merge_finalize(
opt: config::RenderOptions,
compiler: &interface::Compiler,
) -> Result<(), error::Error> {
assert!(
opt.should_merge.write_rendered_cci,
"config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
Expand All @@ -783,16 +786,37 @@ fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
);
let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?;
let include_sources = !opt.html_no_source;
html::render::write_not_crate_specific(
&crates,
&opt.output,
&opt,
&opt.themes,
opt.extension_css.as_deref(),
&opt.resource_suffix,
include_sources,
)?;
Ok(())

let krate = ast::Crate {
attrs: Default::default(),
items: Default::default(),
spans: Default::default(),
id: ast::DUMMY_NODE_ID,
is_placeholder: false,
};
rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
html::render::write_not_crate_specific(
&crates,
&opt.output,
&opt,
&opt.themes,
opt.extension_css.as_deref(),
&opt.resource_suffix,
include_sources,
&crate::html::layout::Layout {
logo: String::new(),
favicon: String::new(),
external_html: opt.external_html.clone(),
default_settings: opt.default_settings.clone(),
krate: String::new(),
krate_version: String::new(),
css_file_extension: opt.extension_css.clone(),
scrape_examples_extension: false,
},
tcx,
)?;
Ok(())
})
}

fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
Expand Down Expand Up @@ -834,10 +858,18 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
let input = match input {
config::InputMode::HasFile(input) => input,
config::InputMode::NoInputMergeFinalize => {
let config = core::create_config(
Input::Str {
name: rustc_span::FileName::Custom(String::new()),
input: String::new(),
},
options,
&render_options,
);
return wrap_return(
dcx,
rustc_span::create_session_globals_then(options.edition, &[], None, || {
run_merge_finalize(render_options)
interface::run_compiler(config, |compiler| {
run_merge_finalize(render_options, compiler)
.map_err(|e| format!("could not write merged cross-crate info: {e}"))
}),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//@ needs-target-std

use run_make_support::{cwd, htmldocck, path, rust_lib_name, rustc, rustdoc};

fn main() {
let merged_dir = path("merged");
let parts_out_dir = path("parts");
let lib_dir = path("lib");
let out_dir = path("out");

rustc().input("quebec.rs").crate_name("quebec").crate_type("rlib").run();
rustdoc()
.input("quebec.rs")
.crate_name("quebec")
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir={}", parts_out_dir.display()))
.run();
assert!(parts_out_dir.join("quebec.json").exists());

rustc()
.input("tango.rs")
.crate_name("tango")
.crate_type("rlib")
.extern_("quebec", rust_lib_name("quebec"))
.run();
rustdoc()
.input("tango.rs")
.crate_name("tango")
.extern_("quebec", rust_lib_name("quebec"))
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir={}", parts_out_dir.display()))
.run();
assert!(parts_out_dir.join("tango.json").exists());

rustdoc()
.input("sierra.rs")
.crate_name("sierra")
.extern_("tango", rust_lib_name("tango"))
.library_search_path(cwd())
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir={}", parts_out_dir.display()))
.run();
assert!(parts_out_dir.join("sierra.json").exists());

let output = rustdoc()
.arg("-Zunstable-options")
.out_dir(&out_dir)
.arg(format!("--read-doc-meta-dir={}", parts_out_dir.display()))
.arg("--enable-index-page")
.run();
output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug.");

htmldocck().arg(&out_dir).arg("tango.rs").run();
htmldocck().arg(&out_dir).arg("quebec.rs").run();
htmldocck().arg(&out_dir).arg("sierra.rs").run();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//@ needs-target-std

use run_make_support::{cwd, htmldocck, path, rust_lib_name, rustc, rustdoc};

fn main() {
let merged_dir = path("merged");
let parts_out_dir = path("parts");
let lib_dir = path("lib");
let out_dir = path("out");

rustc().input("quebec.rs").crate_name("quebec").crate_type("rlib").run();
rustdoc()
.input("quebec.rs")
.crate_name("quebec")
.out_dir("quebec-out")
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir=info/doc.parts/quebec"))
.arg("--enable-index-page")
.run();

rustc()
.input("tango.rs")
.crate_name("tango")
.crate_type("rlib")
.extern_("quebec", rust_lib_name("quebec"))
.run();
rustdoc()
.input("tango.rs")
.crate_name("tango")
.extern_("quebec", rust_lib_name("quebec"))
.out_dir("tango-out")
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir=info/doc.parts/tango"))
.arg("--enable-index-page")
.run();

rustc()
.input("sierra.rs")
.crate_name("sierra")
.crate_type("rlib")
.extern_("tango", rust_lib_name("tango"))
.library_search_path(cwd())
.run();
rustdoc()
.input("sierra.rs")
.crate_name("sierra")
.extern_("tango", rust_lib_name("tango"))
.library_search_path(cwd())
.out_dir("sierra-out")
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir=info/doc.parts/sierra"))
.arg("--enable-index-page")
.run();

rustc()
.input("romeo.rs")
.crate_name("romeo")
.crate_type("rlib")
.extern_("sierra", rust_lib_name("sierra"))
.library_search_path(cwd())
.run();
rustdoc()
.input("romeo.rs")
.crate_name("romeo")
.extern_("sierra", rust_lib_name("sierra"))
.library_search_path(cwd())
.out_dir("romeo-out")
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir=info/doc.parts/romeo"))
.arg("--enable-index-page")
.run();

rustdoc()
.input("indigo.rs")
.crate_name("indigo")
.out_dir(&out_dir)
.arg("-Zunstable-options")
.arg(format!("--write-doc-meta-dir=info/doc.parts/indigo"))
.arg("--enable-index-page")
.run();

let output = rustdoc()
.arg("-Zunstable-options")
.out_dir(&out_dir)
.arg("--read-doc-meta-dir=info/doc.parts/tango")
.arg("--read-doc-meta-dir=info/doc.parts/romeo")
.arg("--read-doc-meta-dir=info/doc.parts/quebec")
.arg("--read-doc-meta-dir=info/doc.parts/sierra")
.arg("--read-doc-meta-dir=info/doc.parts/indigo")
.arg("--enable-index-page")
.run();
output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug.");

htmldocck().arg(&out_dir).arg("tango.rs").run();
htmldocck().arg(&out_dir).arg("quebec.rs").run();
htmldocck().arg(&out_dir).arg("sierra.rs").run();
htmldocck().arg(&out_dir).arg("indigo.rs").run();
htmldocck().arg(&out_dir).arg("romeo.rs").run();
Comment on lines +94 to +98

@camelid camelid Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unless I'm mistaken, it doesn't seem like there are any actual htmldocck @ has checks in these files? In particular, it seems like we should be testing for the presence/content of the index page (either in the form of htmldocck assertions or here in the rmake).

View changes since the review

}
Loading
Loading