From e5ec5e263248df4409578f2cf38a2a8ef0f93461 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Mar 2026 22:32:32 -0700 Subject: [PATCH 1/3] rustdoc: dep-info for standalone markdown inputs --- src/librustdoc/html/render/write_shared.rs | 3 +- src/librustdoc/lib.rs | 32 ++++++++++++++++++++-- src/librustdoc/markdown.rs | 17 ++++++------ tests/run-make/rustdoc-dep-info/example.md | 3 ++ tests/run-make/rustdoc-dep-info/rmake.rs | 5 ++++ 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 tests/run-make/rustdoc-dep-info/example.md diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index b1c77063ca94e..8cb43e0028708 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -118,8 +118,9 @@ pub(crate) fn write_shared( 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(index_page, md_opts, cx.shared.edition()), + crate::markdown::render_and_write(file, md_opts, cx.shared.edition()), &index_page ); } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 6718505bdefd9..e69b20d11aee9 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -70,12 +70,14 @@ use std::io::{self, IsTerminal}; use std::path::Path; use std::process::ExitCode; +use rustc_ast::ast; 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::{EarlyDiagCtxt, getopts}; +use rustc_span::{BytePos, Span, SyntaxContext}; use tracing::info; use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION; @@ -836,8 +838,34 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { // `run_compiler`. return wrap_return( dcx, - interface::run_compiler(config, |_compiler| { - markdown::render_and_write(&md_input, render_options, edition) + interface::run_compiler(config, |compiler| { + // construct a phony "crate" without actually running the parser + // allows us to use other compiler infrastructure like dep-info + let file = + compiler.sess.source_map().load_file(&md_input).map_err(|e| { + format!("{md_input}: {e}", md_input = md_input.display()) + })?; + let inner_span = Span::new( + file.start_pos, + BytePos(file.start_pos.0 + file.normalized_source_len.0), + SyntaxContext::root(), + None, + ); + let krate = ast::Crate { + attrs: Default::default(), + items: Default::default(), + spans: ast::ModSpans { inner_span, ..Default::default() }, + id: ast::DUMMY_NODE_ID, + is_placeholder: false, + }; + 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 has_dep_info { + rustc_interface::passes::write_dep_info(tcx); + } + Ok(()) + }) }), ); } diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 4ca2c104888bb..594c9b1af3397 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -9,10 +9,12 @@ //! [docs]: https://doc.rust-lang.org/stable/rustdoc/#using-standalone-markdown-files use std::fmt::{self, Write as _}; -use std::fs::{File, create_dir_all, read_to_string}; +use std::fs::{File, create_dir_all}; use std::io::prelude::*; -use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use rustc_span::SourceFile; use rustc_span::edition::Edition; use crate::config::RenderOptions; @@ -43,8 +45,8 @@ fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) { /// (e.g., output = "bar" => "bar/foo.html"). /// /// Requires session globals to be available, for symbol interning. -pub(crate) fn render_and_write>( - input: P, +pub(crate) fn render_and_write( + input: Arc, options: RenderOptions, edition: Edition, ) -> Result<(), String> { @@ -52,9 +54,9 @@ pub(crate) fn render_and_write>( return Err(format!("{output}: {e}", output = options.output.display())); } - let input = input.as_ref(); + let input_path = input.name.clone().into_local_path().unwrap_or(PathBuf::new()); let mut output = options.output; - output.push(input.file_name().unwrap()); + output.push(input_path.file_name().unwrap()); output.set_extension("html"); let mut css = String::new(); @@ -63,8 +65,7 @@ pub(crate) fn render_and_write>( .expect("Writing to a String can't fail"); } - let input_str = - read_to_string(input).map_err(|err| format!("{input}: {err}", input = input.display()))?; + let input_str = input.src.as_ref().map(|src| &src[..]).unwrap_or(""); let playground_url = options.markdown_playground_url.or(options.playground_url); let playground = playground_url.map(|url| markdown::Playground { crate_name: None, url }); diff --git a/tests/run-make/rustdoc-dep-info/example.md b/tests/run-make/rustdoc-dep-info/example.md new file mode 100644 index 0000000000000..b4970b4bc3cfc --- /dev/null +++ b/tests/run-make/rustdoc-dep-info/example.md @@ -0,0 +1,3 @@ +% My Example + +First and only paragraph. diff --git a/tests/run-make/rustdoc-dep-info/rmake.rs b/tests/run-make/rustdoc-dep-info/rmake.rs index 11901c97fd6a0..0708a5113f151 100644 --- a/tests/run-make/rustdoc-dep-info/rmake.rs +++ b/tests/run-make/rustdoc-dep-info/rmake.rs @@ -58,4 +58,9 @@ fn main() { assert!(!path("precedence1.d").exists()); assert!(!path("-").exists()); // `-` shouldn't be treated as a file path assert!(!result.stdout().is_empty()); // Something emitted to stdout + + // test --emit=dep-info combined with plain markdown input + rustdoc().input("example.md").arg("-Zunstable-options").emit("dep-info").run(); + let content = rfs::read_to_string("doc/example.d"); + assert_contains(&content, "example.md:"); } From a15a3d15b71d788348df980c29d777cb87ab79fd Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 24 Mar 2026 22:53:21 -0700 Subject: [PATCH 2/3] rustdoc: loaded files in dep-info for standalone --- src/librustdoc/lib.rs | 5 ++++ tests/run-make/rustdoc-dep-info/rmake.rs | 31 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e69b20d11aee9..d8c881601a3db 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -862,6 +862,11 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { let has_dep_info = render_options.dep_info().is_some(); 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. + for external_path in &loaded_paths { + let _ = compiler.sess.source_map().load_binary_file(external_path); + } rustc_interface::passes::write_dep_info(tcx); } Ok(()) diff --git a/tests/run-make/rustdoc-dep-info/rmake.rs b/tests/run-make/rustdoc-dep-info/rmake.rs index 0708a5113f151..1e3d46bd43287 100644 --- a/tests/run-make/rustdoc-dep-info/rmake.rs +++ b/tests/run-make/rustdoc-dep-info/rmake.rs @@ -3,7 +3,7 @@ //@ needs-target-std -use run_make_support::assertion_helpers::assert_contains; +use run_make_support::assertion_helpers::{assert_contains, assert_not_contains}; use run_make_support::{path, rfs, rustdoc}; fn main() { @@ -63,4 +63,33 @@ fn main() { rustdoc().input("example.md").arg("-Zunstable-options").emit("dep-info").run(); let content = rfs::read_to_string("doc/example.d"); assert_contains(&content, "example.md:"); + assert_not_contains(&content, "lib.rs:"); + assert_not_contains(&content, "foo.rs:"); + assert_not_contains(&content, "bar.rs:"); + assert_not_contains(&content, "doc.md:"); + assert_not_contains(&content, "after.md:"); + assert_not_contains(&content, "before.html:"); + assert_not_contains(&content, "extend.css:"); + assert_not_contains(&content, "theme.css:"); + + // combine --emit=dep-info=filename with plain markdown input + rustdoc() + .input("example.md") + .arg("-Zunstable-options") + .arg("--html-before-content=before.html") + .arg("--markdown-after-content=after.md") + .arg("--extend-css=extend.css") + .arg("--theme=theme.css") + .emit("dep-info=example.d") + .run(); + let content = rfs::read_to_string("example.d"); + assert_contains(&content, "example.md:"); + assert_not_contains(&content, "lib.rs:"); + assert_not_contains(&content, "foo.rs:"); + assert_not_contains(&content, "bar.rs:"); + 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:"); } From bfaf027a3be3b0328756f4edb41360b9749f83aa Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 29 Mar 2026 19:06:43 -0700 Subject: [PATCH 3/3] rustdoc: include index-page in dep-info --- src/librustdoc/config.rs | 10 ++++++---- tests/run-make/rustdoc-dep-info/index-page.md | 3 +++ tests/run-make/rustdoc-dep-info/rmake.rs | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 tests/run-make/rustdoc-dep-info/index-page.md diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index d721034c2d71d..67d1105a5fa2b 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -744,10 +744,12 @@ impl Options { } let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s)); - if let Some(ref index_page) = index_page - && !index_page.is_file() - { - dcx.fatal("option `--index-page` argument must be a file"); + if let Some(ref index_page) = index_page { + if index_page.is_file() { + loaded_paths.push(index_page.clone()); + } else { + dcx.fatal("option `--index-page` argument must be a file"); + } } let target = parse_target_triple(early_dcx, matches); diff --git a/tests/run-make/rustdoc-dep-info/index-page.md b/tests/run-make/rustdoc-dep-info/index-page.md new file mode 100644 index 0000000000000..95fa11ed0b76a --- /dev/null +++ b/tests/run-make/rustdoc-dep-info/index-page.md @@ -0,0 +1,3 @@ +% Index page + +Index page diff --git a/tests/run-make/rustdoc-dep-info/rmake.rs b/tests/run-make/rustdoc-dep-info/rmake.rs index 1e3d46bd43287..233225fde8696 100644 --- a/tests/run-make/rustdoc-dep-info/rmake.rs +++ b/tests/run-make/rustdoc-dep-info/rmake.rs @@ -19,6 +19,7 @@ fn main() { .arg("--markdown-after-content=after.md") .arg("--extend-css=extend.css") .arg("--theme=theme.css") + .arg("--index-page=index-page.md") .emit("dep-info") .run(); @@ -31,6 +32,7 @@ fn main() { assert_contains(&content, "before.html:"); assert_contains(&content, "extend.css:"); assert_contains(&content, "theme.css:"); + assert_contains(&content, "index-page.md:"); // 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(); @@ -80,6 +82,7 @@ fn main() { .arg("--markdown-after-content=after.md") .arg("--extend-css=extend.css") .arg("--theme=theme.css") + .arg("--index-page=index-page.md") .emit("dep-info=example.d") .run(); let content = rfs::read_to_string("example.d"); @@ -92,4 +95,5 @@ fn main() { assert_contains(&content, "before.html:"); assert_contains(&content, "extend.css:"); assert_contains(&content, "theme.css:"); + assert_contains(&content, "index-page.md:"); }