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

@weihanglo weihanglo Jul 29, 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.

Probably not the right question for this PR, and I haven't read the implementation here, though I am curious:

  • How does this interact with --out-dir. I guess --out-dir is ignored here?
  • How does this interact with all those --emit files (w/ or w/o --out-dir? Where will those files be emitted?

View changes since the review

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.

Context: I'd like to pass --emit=dep-info without specifying the full (absolute) path for those dep-info files. Because of this issue #159743 we cannot specify full path, and currently blocks rust-lang/cargo#17020.

(That said, it is a very Cargo specific issue I feel like)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The --out-dir is still used as the path for --emit files. --write-doc-meta-dir doesn't pay attention to --out-dir.

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 see. So eventually if individual rustdoc doesn't need to emit html contents, cargo then can use --emit to control dep-info emission location exclusively. Thanks!

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, rustdoc runs in *finalize mode*. No crate source code is
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`.
`--write-doc-meta-dir` and `--read-doc-meta-dir` cannot both be passed to the same rustdoc invocation.

```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
16 changes: 10 additions & 6 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,10 @@ impl Options {
Ok(include_parts_dir) => include_parts_dir,
Err(e) => dcx.fatal(e),
};
let mut should_merge = compute_should_merge(matches);
let mut should_merge = match compute_should_merge(matches) {
Ok(should_merge) => should_merge,
Err(e) => dcx.fatal(e),
};
if parts_out_dir.is_none() && include_parts_dir.is_empty() {
// we'll need to get rid of this stuff once Cargo stops using them
parts_out_dir =
Expand Down Expand Up @@ -1114,15 +1117,16 @@ pub(crate) struct ShouldMerge {

/// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or
/// reports an error if an invalid option was provided
fn compute_should_merge(m: &getopts::Matches) -> ShouldMerge {
fn compute_should_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> {
match (m.opt_present("read-doc-meta-dir"), m.opt_present("write-doc-meta-dir")) {
// shared mode
(false, false) => ShouldMerge { read_rendered_cci: true, write_rendered_cci: true },
(false, false) => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
// intermediate mode
(false, true) => ShouldMerge { read_rendered_cci: false, write_rendered_cci: false },
(false, true) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }),
// finalize mode
(true, false) => ShouldMerge { read_rendered_cci: false, write_rendered_cci: true },
(true, true) => ShouldMerge { read_rendered_cci: false, write_rendered_cci: true },
(true, false) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }),
// not valid
(true, true) => Err("cannot pass both --read-doc-meta-dir and --write-doc-meta-dir"),
}
}

Expand Down
117 changes: 5 additions & 112 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ use tracing::info;

use super::print_item::{full_path, print_item, print_item_path, print_ty_path};
use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like};
use super::{AllTypes, StylePath, scrape_examples_help};
use super::{AllTypes, StylePath};
use crate::clean::types::ExternalLocation;
use crate::clean::utils::has_doc_flag;
use crate::clean::{self, ExternalCrate};
use crate::config::{EmitType, ModuleSorting, RenderOptions, ShouldMerge};
use crate::config::{EmitType, ModuleSorting, RenderOptions};
use crate::docfs::{DocFS, PathError};
use crate::error::Error;
use crate::formats::FormatRenderer;
Expand All @@ -36,9 +36,9 @@ use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
use crate::html::render::write_shared::write_shared;
use crate::html::span_map::{LinkFromSrc, Span, collect_spans_and_sources};
use crate::html::url_parts_builder::UrlPartsBuilder;
use crate::html::{layout, sources, static_files};
use crate::html::{layout, sources};
use crate::scrape_examples::AllCallLocations;
use crate::{DOC_RUST_LANG_ORG_VERSION, try_err};
use crate::try_err;

/// Major driving force in all rustdoc rendering. This contains information
/// about where in the tree-like hierarchy rendering is occurring and controls
Expand Down Expand Up @@ -148,9 +148,6 @@ pub(crate) struct SharedContext<'tcx> {
/// The [`Cache`] used during rendering.
pub(crate) cache: Cache,
pub(crate) call_locations: AllCallLocations,
/// Controls whether we read / write to cci files in the doc root. Defaults read=true,
/// write=true
should_merge: ShouldMerge,
}

impl SharedContext<'_> {
Expand Down Expand Up @@ -615,7 +612,6 @@ impl<'tcx> Context<'tcx> {
span_correspondence_map: matches,
cache,
call_locations,
should_merge: options.should_merge,
expanded_codes,
};

Expand Down Expand Up @@ -666,16 +662,13 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
fn after_krate(mut self) -> Result<(), Error> {
let crate_name = self.tcx().crate_name(LOCAL_CRATE);
let final_file = self.dst.join(crate_name.as_str()).join("all.html");
let settings_file = self.dst.join("settings.html");
let help_file = self.dst.join("help.html");
let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");

let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
if !root_path.ends_with('/') {
root_path.push('/');
}
let shared = &self.shared;
let mut page = layout::Page {
let page = layout::Page {
title: "List of all items in this crate",
short_title: "All",
css_class: "mod sys",
Expand Down Expand Up @@ -705,106 +698,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let v = layout::render(&shared.layout, &page, sidebar, all.print(), &shared.style_files);
shared.fs.write(final_file, v)?;

// if to avoid writing help, settings files to doc root unless we're on the final invocation
if shared.should_merge.write_rendered_cci {
// Generating settings page.
page.title = "Settings";
page.description = "Settings of Rustdoc";
page.root_path = "./";
page.rust_logo = true;

let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
fmt::from_fn(|buf| {
write!(
buf,
"<div class=\"main-heading\">\
<h1>Rustdoc settings</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
You need to enable JavaScript be able to update your settings.\
</section>\
</noscript>\
<script defer src=\"{static_root_path}{settings_js}\"></script>",
static_root_path = page.get_static_root_path(),
settings_js = static_files::STATIC_FILES.settings_js,
)?;
// Pre-load all theme CSS files, so that switching feels seamless.
//
// When loading settings.html as a popover, the equivalent HTML is
// generated in main.js.
for file in &shared.style_files {
if let Ok(theme) = file.basename() {
write!(
buf,
"<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
as=\"style\">",
root_path = page.static_root_path.unwrap_or(""),
suffix = page.resource_suffix,
)?;
}
}
Ok(())
}),
&shared.style_files,
);
shared.fs.write(settings_file, v)?;

// Generating help page.
page.title = "Help";
page.description = "Documentation for Rustdoc";
page.root_path = "./";
page.rust_logo = true;

let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
format_args!(
"<div class=\"main-heading\">\
<h1>Rustdoc help</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
<p>You need to enable JavaScript to use keyboard commands or search.</p>\
<p>For more information, browse the <a href=\"{DOC_RUST_LANG_ORG_VERSION}/rustdoc/\">rustdoc handbook</a>.</p>\
</section>\
</noscript>",
),
&shared.style_files,
);
shared.fs.write(help_file, v)?;
}

// if to avoid writing files to doc root unless we're on the final invocation
if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci {
page.title = "About scraped examples";
page.description = "How the scraped examples feature works in Rustdoc";
let v = layout::render(
&shared.layout,
&page,
"",
scrape_examples_help(shared),
&shared.style_files,
);
shared.fs.write(scrape_examples_help_file, v)?;
}

if let Some(ref redirections) = shared.redirections
&& !redirections.borrow().is_empty()
{
Expand Down
9 changes: 5 additions & 4 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ impl AllTypes {
}
}

fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
fn scrape_examples_help() -> String {
let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned();
content.push_str(&format!(
"## More information\n\n\
Expand All @@ -680,9 +680,10 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
content: &content,
links: &[],
ids: &mut IdMap::default(),
error_codes: shared.codes,
edition: shared.edition(),
playground: &shared.playground,
// code snippets come from Rust itself, not the crate
error_codes: crate::html::markdown::ErrorCodes::No,
edition: rustc_span::edition::LATEST_STABLE_EDITION,
playground: &Default::default(),
heading_offset: HeadingOffset::H1,
}
.write_into(f))
Expand Down
Loading
Loading