Skip to content
Merged
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
56 changes: 29 additions & 27 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ pub(crate) fn write_not_crate_specific(
include_sources: bool,
) -> Result<(), Error> {
write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
write_resources(dst, opt, style_files, css_file_extension, resource_suffix)?;
Ok(())
}

Expand Down Expand Up @@ -183,43 +183,45 @@ fn write_rendered_cross_crate_info(

/// Writes the static files, the style files, and the css extensions.
/// Have to be careful about these, because they write to the root out dir.
fn write_static_files(
fn write_resources(
dst: &Path,
opt: &RenderOptions,
style_files: &[StylePath],
css_file_extension: Option<&Path>,
resource_suffix: &str,
) -> Result<(), Error> {
let static_dir = dst.join("static.files");
try_err!(fs::create_dir_all(&static_dir), &static_dir);

// Handle added third-party themes
for entry in style_files {
let theme = entry.basename()?;
let extension =
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);

// Skip the official themes. They are written below as part of STATIC_FILES_LIST.
if matches!(theme.as_str(), "light" | "dark" | "ayu") {
continue;
}
if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlNonStaticFiles) {
Copy link
Copy Markdown
Member

@fmease fmease Apr 22, 2026

Choose a reason for hiding this comment

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

If we were to eagerly set opt.emit to [HtmlNonStaticFiles, HtmlStaticFiles] in the CLI parser if --emit wasn't passed, then later stages wouldn't need to know that "absence of emission types means two emission types" & thus wouldn't need to remember to check opt.emit.is_empty(). That'd make fn should_emit_crate unnecessary, too.

opt.emit could be a bitset even to avoid having to allocate 2×EmitType in the common case (peanuts ofc) (won't work because DepInfo has a payload; well SmallVec<_, 2> would do).

Anyway just some thoughts for a potential future cleanup to make all of this more robust.

Copy link
Copy Markdown
Member

@fmease fmease Apr 23, 2026

Choose a reason for hiding this comment

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

While trying to implement my suggestion I realized that I can't just replace the is_empty && contains_html_non_static_files checks under this scheme since the is_empty condition also accounts for "JSON non-static files", e.g., in run_format that's executed by both the HTML & the JSON backend.

I guess we could push a EmitType::EmitJsonNonStaticFiles under output_format == OutputFormat::Json that doesn't necessarily have a corresponding "surface syntax" (so no --emit=json-non-static-files unless we want to). Tho then I'd have to introduce FormatRenderer::NON_STATIC_FILES: EmitType, I guess that's fine.

Alternatively, we could also have a "lower" EmitType that just has EmitNonStaticFiles for Html+Json+Doctests but that seems really overengineered at this point.

Hmm, that makes me question the html- prefix of these options 🤔. I need to reread the meeting messages.

// Handle added third-party themes
for entry in style_files {
let theme = entry.basename()?;
let extension =
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);

// Skip the official themes. They are written below as part of STATIC_FILES_LIST.
if matches!(theme.as_str(), "light" | "dark" | "ayu") {
continue;
}

let bytes = try_err!(fs::read(&entry.path), &entry.path);
let filename = format!("{theme}{resource_suffix}.{extension}");
let dst_filename = dst.join(filename);
try_err!(fs::write(&dst_filename, bytes), &dst_filename);
}
let bytes = try_err!(fs::read(&entry.path), &entry.path);
let filename = format!("{theme}{resource_suffix}.{extension}");
let dst_filename = dst.join(filename);
try_err!(fs::write(&dst_filename, bytes), &dst_filename);
}

// When the user adds their own CSS files with --extend-css, we write that as an
// invocation-specific file (that is, with a resource suffix).
if let Some(css) = css_file_extension {
let buffer = try_err!(fs::read_to_string(css), css);
let path = static_files::suffix_path("theme.css", resource_suffix);
let dst_path = dst.join(path);
try_err!(fs::write(&dst_path, buffer), &dst_path);
// When the user adds their own CSS files with --extend-css, we write that as an
// invocation-specific file (that is, with a resource suffix).
if let Some(css) = css_file_extension {
let buffer = try_err!(fs::read_to_string(css), css);
let path = static_files::suffix_path("theme.css", resource_suffix);
let dst_path = dst.join(path);
try_err!(fs::write(&dst_path, buffer), &dst_path);
}
}

if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlStaticFiles) {
let static_dir = dst.join("static.files");
try_err!(fs::create_dir_all(&static_dir), &static_dir);

static_files::for_each(|f: &static_files::StaticFile| {
let filename = static_dir.join(f.output_filename());
let contents: &[u8] =
Expand Down
7 changes: 6 additions & 1 deletion src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ use rustc_span::{BytePos, Span, SyntaxContext};
use tracing::info;

use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION;
use crate::config::EmitType;
use crate::error::Error;
use crate::formats::cache::Cache;

Expand Down Expand Up @@ -868,7 +869,11 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
};
rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
let has_dep_info = render_options.dep_info().is_some();
markdown::render_and_write(file, render_options, edition)?;
if render_options.emit.contains(&EmitType::HtmlNonStaticFiles)
|| render_options.emit.is_empty()
{
markdown::render_and_write(file, render_options, edition)?;
}
if has_dep_info {
// Register the loaded external files in the source map so they show up in depinfo.
// We can't load them via the source map because it gets created after we process the options.
Expand Down
53 changes: 44 additions & 9 deletions tests/run-make/rustdoc-dep-info/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ use run_make_support::{path, rfs, rustdoc};
fn main() {
rfs::create_dir("doc");

// We're only emitting dep info, so we shouldn't be running static analysis to
// figure out that this program is erroneous.
// Ensure that all kinds of input reading flags end up in dep-info.
rustdoc()
.input("lib.rs")
.arg("-Zunstable-options")
.arg("--html-before-content=before.html")
.arg("--markdown-after-content=after.md")
.arg("--extend-css=extend.css")
.arg("--theme=theme.css")
.arg("--theme=custom_theme.css")
.arg("--index-page=index-page.md")
.emit("dep-info")
.run();
Expand All @@ -31,8 +29,31 @@ fn main() {
assert_contains(&content, "after.md:");
assert_contains(&content, "before.html:");
assert_contains(&content, "extend.css:");
assert_contains(&content, "theme.css:");
assert_contains(&content, "custom_theme.css:");
assert_contains(&content, "index-page.md:");
// Only emit dep-info. Don't emit the actual page.
assert!(!path("doc/foo/index.html").exists());
assert!(!path("doc/custom_theme.css").exists());
// weird that --extend-css generates a file named theme.css
assert!(!path("doc/theme.css").exists());

// Now try emitting dep-info and html files at the same time.
rustdoc()
.input("lib.rs")
.arg("-Zunstable-options")
.arg("--html-before-content=before.html")
.arg("--markdown-after-content=after.md")
.arg("--extend-css=extend.css")
.arg("--theme=custom_theme.css")
.arg("--index-page=index-page.md")
.emit("dep-info,html-non-static-files,html-static-files")
.run();
assert!(path("doc/foo/index.html").exists());
// These files are copied into the doc output folder,
// which is why they show up in dep-info.
assert!(path("doc/custom_theme.css").exists());
// weird that --extend-css generates a file named theme.css
assert!(path("doc/theme.css").exists());

// Now we check that we can provide a file name to the `dep-info` argument.
rustdoc().input("lib.rs").arg("-Zunstable-options").emit("dep-info=bla.d").run();
Expand Down Expand Up @@ -72,7 +93,9 @@ fn main() {
assert_not_contains(&content, "after.md:");
assert_not_contains(&content, "before.html:");
assert_not_contains(&content, "extend.css:");
assert_not_contains(&content, "theme.css:");
assert_not_contains(&content, "custom_theme.css:");
// Only emit dep-info, not the actual html.
assert!(!path("doc/example.html").exists());

// combine --emit=dep-info=filename with plain markdown input
rustdoc()
Expand All @@ -81,10 +104,12 @@ fn main() {
.arg("--html-before-content=before.html")
.arg("--markdown-after-content=after.md")
.arg("--extend-css=extend.css")
.arg("--theme=theme.css")
.arg("--theme=custom_theme.css")
.arg("--markdown-css=markdown.css")
.arg("--index-page=index-page.md")
.emit("dep-info=example.d")
.emit("dep-info=example.d,html-non-static-files,html-static-files")
.run();
assert!(path("doc/example.html").exists());
let content = rfs::read_to_string("example.d");
assert_contains(&content, "example.md:");
assert_not_contains(&content, "lib.rs:");
Expand All @@ -93,7 +118,17 @@ fn main() {
assert_not_contains(&content, "doc.md:");
assert_contains(&content, "after.md:");
assert_contains(&content, "before.html:");
assert_contains(&content, "extend.css:");
assert_contains(&content, "theme.css:");
assert_contains(&content, "index-page.md:");
// This is a hotlink, not a file that gets copied,
// so it shouldn't add to the dep-info, it shouldn't be copied,
// and it shouldn't be resolved relative to the root path.
//
// It's weird that this is different from the other two css
// files, but it's stable, so I can't change it.
assert!(!path("doc/markdown.css").exists());
assert_not_contains(&content, "markdown.css:");
// These files aren't actually used, and the fact that they show up
// is arguably a bug, but test it anyway.
assert_contains(&content, "extend.css:");
assert_contains(&content, "custom_theme.css:");
}
Loading