Skip to content

[rustdoc] Add new --book-location option to add a link to associated guide and generate it if local #139769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
605 changes: 574 additions & 31 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -830,3 +830,11 @@ will be split as follows:
"you today?",
]
```

## Generating link to guide

You can generate a link to a guide or generate the guide with `mdbook` using the `--book-location`
command line argument. It accepts either a URL or a path. If a path is provided, the book will
be generated.

In both cases, a link to the book will be added in the sidebar at the crate level documentation.
1 change: 1 addition & 0 deletions src/librustdoc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ askama = { version = "0.13", default-features = false, features = ["alloc", "con
base64 = "0.21.7"
itertools = "0.12"
indexmap = "2"
mdbook = "0.4.48"
minifier = { version = "0.3.5", default-features = false }
pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false }
pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] }
Expand Down
20 changes: 20 additions & 0 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ impl fmt::Debug for Options {
}
}

#[derive(Clone, Debug)]
pub(crate) enum PathOrURL {
Path(PathBuf),
URL(String),
}

impl PathOrURL {
fn new(s: String) -> Self {
if s.starts_with("https://") || s.starts_with("http://") {
Self::URL(s)
} else {
Self::Path(s.into())
}
}
}

/// Configuration options for the HTML page-creation process.
#[derive(Clone, Debug)]
pub(crate) struct RenderOptions {
Expand Down Expand Up @@ -310,6 +326,8 @@ pub(crate) struct RenderOptions {
pub(crate) parts_out_dir: Option<PathToParts>,
/// disable minification of CSS/JS
pub(crate) disable_minification: bool,
/// Location where the associated book is located.
pub(crate) book_location: Option<PathOrURL>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -814,6 +832,7 @@ impl Options {
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());

let disable_minification = matches.opt_present("disable-minification");
let book_location = matches.opt_str("book-location").map(PathOrURL::new);

let options = Options {
bin_crate,
Expand Down Expand Up @@ -893,6 +912,7 @@ impl Options {
include_parts_dir,
parts_out_dir,
disable_minification,
book_location,
};
Some((input, options, render_options))
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,7 @@ fn is_default_id(id: &str) -> bool {
| "copy-path"
| "rustdoc-toc"
| "rustdoc-modnav"
| "book-loc"
// This is the list of IDs used by rustdoc sections (but still generated by
// rustdoc).
| "fields"
Expand Down
4 changes: 4 additions & 0 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub(crate) struct SharedContext<'tcx> {
/// Controls whether we read / write to cci files in the doc root. Defaults read=true,
/// write=true
should_merge: ShouldMerge,
pub(crate) book_location: Option<crate::config::PathOrURL>,
}

impl SharedContext<'_> {
Expand Down Expand Up @@ -489,6 +490,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
call_locations,
no_emit_shared,
html_no_source,
book_location,
..
} = options;

Expand Down Expand Up @@ -575,6 +577,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
cache,
call_locations,
should_merge: options.should_merge,
book_location,
};

let dst = output;
Expand Down Expand Up @@ -646,6 +649,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
parent_is_crate: false,
blocks: vec![blocks],
path: String::new(),
book_location: shared.book_location.as_ref(),
};

bar.render_into(&mut sidebar).unwrap();
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/html/render/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub(super) struct Sidebar<'a> {
pub(super) is_mod: bool,
pub(super) blocks: Vec<LinkBlock<'a>>,
pub(super) path: String,
pub(super) book_location: Option<&'a crate::config::PathOrURL>,
}

impl Sidebar<'_> {
Expand Down Expand Up @@ -194,6 +195,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Str
parent_is_crate: sidebar_path.len() == 1,
blocks,
path,
book_location: cx.shared.book_location.as_ref(),
};
sidebar.render_into(buffer).unwrap();
}
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/html/static/css/noscript.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ nav.sub {
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
--sidebar-resizer-hover: hsl(207, 90%, 66%);
--sidebar-resizer-active: hsl(207, 90%, 54%);
--book-img-filter: invert(0%);
}
/* End theme: light */

Expand Down Expand Up @@ -244,6 +245,7 @@ nav.sub {
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
--sidebar-resizer-hover: hsl(207, 30%, 54%);
--sidebar-resizer-active: hsl(207, 90%, 54%);
--book-img-filter: invert(80%);
}
/* End theme: dark */
}
24 changes: 24 additions & 0 deletions src/librustdoc/html/static/css/rustdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,27 @@ ul.block, .block li, .block ul {
margin-bottom: 1rem;
}

.sidebar .book {
display: flex;
}

.book::before {
width: 1em;
height: 1em;
/* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License -
https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.*/
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">\
<path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 \
32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 \
384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 \
0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 \
16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>');
display: block;
margin-right: 6px;
padding-top: 3px;
filter: var(--book-img-filter);
}

.mobile-topbar {
display: none;
}
Expand Down Expand Up @@ -2985,6 +3006,7 @@ by default.
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
--sidebar-resizer-hover: hsl(207, 90%, 66%);
--sidebar-resizer-active: hsl(207, 90%, 54%);
--book-img-filter: invert(0%);
}
/* End theme: light */

Expand Down Expand Up @@ -3093,6 +3115,7 @@ by default.
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
--sidebar-resizer-hover: hsl(207, 30%, 54%);
--sidebar-resizer-active: hsl(207, 90%, 54%);
--book-img-filter: invert(80%);
}
/* End theme: dark */

Expand Down Expand Up @@ -3205,6 +3228,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
--scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0);
--sidebar-resizer-hover: hsl(34, 50%, 33%);
--sidebar-resizer-active: hsl(34, 100%, 66%);
--book-img-filter: invert(100%);
}

:root[data-theme="ayu"] h1,
Expand Down
14 changes: 14 additions & 0 deletions src/librustdoc/html/templates/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
<div class="sidebar-elems">
{% if is_crate %}
{% if let Some(book_location) = book_location %}
<ul class="block"> {# #}
<li> {# #}
<h3> {# #}
<a id="book-loc" class="book" href="
{% match book_location %}
{% when crate::config::PathOrURL::Path(s) %}{{s.display()}}
{% when crate::config::PathOrURL::URL(s) %}{{s}}
{% endmatch %}
">Book</a> {# #}
</h3> {# #}
</li> {# #}
</ul>
{% endif %}
<ul class="block"> {# #}
<li><a id="all-types" href="all.html">All Items</a></li> {# #}
</ul>
Expand Down
40 changes: 39 additions & 1 deletion src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,14 @@ fn opts() -> Vec<RustcOptGroup> {
"disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
"",
),
opt(
Unstable,
Opt,
"",
"book-location",
"URL where the book is hosted or the folder where the mdBook source is located",
"PATH or URL",
),
// deprecated / removed options
opt(
Stable,
Expand Down Expand Up @@ -741,6 +749,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
}
}

fn generate_book(render_options: &mut config::RenderOptions) -> Result<(), String> {
let Some(config::PathOrURL::Path(ref mut book_dir)) = render_options.book_location else {
return Ok(());
};
if !book_dir.is_dir() {
return Err(format!(
"`{}` is not a folder, expected a folder or a URL for `--book-location` argument",
book_dir.display(),
));
}
let mut book = match mdbook::MDBook::load(&book_dir) {
Ok(book) => book,
Err(error) => return Err(format!("failed to load book: {error:?}")),
};
let output_dir = render_options.output.join("doc-book");
*book_dir = output_dir.join("index.html");
book.config.build.build_dir = output_dir;
if let Err(error) = book.build() {
return Err(format!(
"failed to generate book into `{}`: {error:?}",
book.config.build.build_dir.display()
));
}
Ok(())
}

/// 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 `--include-parts-dir` are combined and written to the doc root.
Expand Down Expand Up @@ -793,7 +827,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {

// Note that we discard any distinction between different non-zero exit
// codes from `from_matches` here.
let (input, options, render_options) =
let (input, options, mut render_options) =
match config::Options::from_matches(early_dcx, &matches, args) {
Some(opts) => opts,
None => return,
Expand Down Expand Up @@ -857,6 +891,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
let scrape_examples_options = options.scrape_examples_options.clone();
let bin_crate = options.bin_crate;

if let Err(error) = generate_book(&mut render_options) {
early_dcx.early_fatal(error);
}

let config = core::create_config(input, options, &render_options);

let registered_lints = config.register_lints.is_some();
Expand Down
2 changes: 2 additions & 0 deletions src/tools/tidy/src/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const LICENSES: &[&str] = &[
"Apache-2.0",
"Apache-2.0/MIT",
"BSD-2-Clause OR Apache-2.0 OR MIT", // zerocopy
"CC0-1.0", // notify
"ISC",
"MIT / Apache-2.0",
"MIT AND (MIT OR Apache-2.0)",
Expand All @@ -38,6 +39,7 @@ const LICENSES: &[&str] = &[
"MIT OR Zlib OR Apache-2.0", // miniz_oxide
"MIT",
"MIT/Apache-2.0",
"MPL-2.0", // mdbook
"Unicode-3.0", // icu4x
"Unicode-DFS-2016", // tinystr
"Unlicense OR MIT",
Expand Down
3 changes: 3 additions & 0 deletions tests/run-make/rustdoc-default-output/output-default.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ Options:
--disable-minification
disable the minification of CSS/JS files
(perma-unstable, do not use with cached files)
--book-location PATH or URL
URL where the book is hosted or the folder where the
mdBook source is located
--plugin-path DIR
removed, see issue #44136
<https://github.com/rust-lang/rust/issues/44136> for
Expand Down
7 changes: 7 additions & 0 deletions tests/rustdoc/book-location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//@ compile-flags: -Zunstable-options --book-location https://somewhere.world

#![crate_name = "foo"]

//@ has 'foo/index.html'
//@ has - '//*[@id="book-loc"]' 'Book'
//@ has - '//*[@href="https://somewhere.world"]' 'Book'
Loading