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
10 changes: 6 additions & 4 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown
Member

@fmease fmease Mar 29, 2026

Choose a reason for hiding this comment

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

I have tested your patch locally and ran rustdoc test.md --index-page my-index-page.md --emit=dep-info (with the appropriate source files) but my-index-page.md wasn't listed in the resulting dep info (for comparison, --html-after-content did obv work as the tests also confirm); I might've overlooked something tho.

Note that the custom index page isn't properly tracked in "normal mode" either (where the source file is Rust), neither on main nor on your branch AFAICT.

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.

I see.

I notice that write_dep_info is run before the renderer is run. To fix this, I either need to add the file to the source map separately (which can be done the same way --html-before-content and related options are done), or move when the renderer runs.

The less risky option, at least in the short term, is adding the file separately.

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
);
}
Expand Down
37 changes: 35 additions & 2 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -836,8 +838,39 @@ 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 {
// 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 {
Copy link
Copy Markdown
Member

@fmease fmease Mar 29, 2026

Choose a reason for hiding this comment

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

Flags like --theme and --extend-css don't seem to be used for standalone markdown files IINM, so they don't really need to be included in the dep-info. Of course, impl'ing that isn't worth the complexity, this approach is fine as is imo.

let _ = compiler.sess.source_map().load_binary_file(external_path);
}
rustc_interface::passes::write_dep_info(tcx);
}
Ok(())
})
}),
);
}
Expand Down
17 changes: 9 additions & 8 deletions src/librustdoc/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -43,18 +45,18 @@ 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<P: AsRef<Path>>(
input: P,
pub(crate) fn render_and_write(
input: Arc<SourceFile>,
options: RenderOptions,
edition: Edition,
) -> Result<(), String> {
if let Err(e) = create_dir_all(&options.output) {
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();
Expand All @@ -63,8 +65,7 @@ pub(crate) fn render_and_write<P: AsRef<Path>>(
.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 });

Expand Down
3 changes: 3 additions & 0 deletions tests/run-make/rustdoc-dep-info/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
% My Example

First and only paragraph.
3 changes: 3 additions & 0 deletions tests/run-make/rustdoc-dep-info/index-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
% Index page

Index page
40 changes: 39 additions & 1 deletion tests/run-make/rustdoc-dep-info/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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();

Expand All @@ -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();
Expand Down Expand Up @@ -58,4 +60,40 @@ 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:");
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")
.arg("--index-page=index-page.md")
.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:");
assert_contains(&content, "index-page.md:");
}
Loading