diff --git a/Cargo.lock b/Cargo.lock index 325503a0e9564..556ef03af92ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4489,6 +4489,7 @@ dependencies = [ "rayon", "regex", "rustdoc-json-types", + "semver", "serde", "serde_json", "smallvec", diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index cc5c583bea8e2..c8f9ed50ce2fc 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -15,6 +15,7 @@ minifier = "0.0.43" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +semver = "1.0" smallvec = "1.6.1" tempfile = "3" itertools = "0.10.1" diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index cee3dcb416f80..e08feb66c66a8 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -276,6 +276,8 @@ crate struct RenderOptions { crate call_locations: AllCallLocations, /// If `true`, Context::init will not emit shared files. crate no_emit_shared: bool, + /// Comes from the `--minimum-supported-rust-version` option. + crate minimum_supported_rust_version: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -656,6 +658,16 @@ impl Options { let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); + let minimum_supported_rust_version = matches.opt_str("minimum-supported-rust-version"); + if let Some(ref minimum_supported_rust_version) = minimum_supported_rust_version { + if let Err(e) = semver::Version::parse(minimum_supported_rust_version) { + diag.struct_err(&format!( + "--minimum-supported-rust-version expects a valid semver value: {e}" + )) + .emit(); + return Err(1); + } + } if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { diag.struct_err( @@ -734,6 +746,7 @@ impl Options { generate_link_to_definition, call_locations, no_emit_shared: false, + minimum_supported_rust_version, }, crate_name, output_format, diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 34784bbed0cf5..040025d533312 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -124,6 +124,8 @@ crate struct SharedContext<'tcx> { crate cache: Cache, crate call_locations: AllCallLocations, + + crate minimum_supported_rust_version: Option, } impl SharedContext<'_> { @@ -406,6 +408,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { generate_link_to_definition, call_locations, no_emit_shared, + minimum_supported_rust_version, .. } = options; @@ -490,6 +493,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { span_correspondance_map: matches, cache, call_locations, + minimum_supported_rust_version, }; // Add the default themes to the `Vec` of stylepaths diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 9891c4b676fb4..4826210e4e40a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -466,15 +466,26 @@ fn document( item: &clean::Item, parent: Option<&clean::Item>, heading_offset: HeadingOffset, +) { + document_inner(w, cx, item, parent, heading_offset, None) +} + +fn document_inner( + w: &mut Buffer, + cx: &Context<'_>, + item: &clean::Item, + parent: Option<&clean::Item>, + heading_offset: HeadingOffset, + extra: Option, ) { if let Some(ref name) = item.name { info!("Documenting {}", name); } document_item_info(w, cx, item, parent); if parent.is_none() { - document_full_collapsible(w, item, cx, heading_offset); + document_full_collapsible(w, item, cx, heading_offset, extra); } else { - document_full(w, item, cx, heading_offset); + document_full(w, item, cx, heading_offset, extra); } } @@ -539,8 +550,9 @@ fn document_full_collapsible( item: &clean::Item, cx: &Context<'_>, heading_offset: HeadingOffset, + extra: Option, ) { - document_full_inner(w, item, cx, true, heading_offset); + document_full_inner(w, item, cx, true, heading_offset, extra); } fn document_full( @@ -548,8 +560,9 @@ fn document_full( item: &clean::Item, cx: &Context<'_>, heading_offset: HeadingOffset, + extra: Option, ) { - document_full_inner(w, item, cx, false, heading_offset); + document_full_inner(w, item, cx, false, heading_offset, extra); } fn document_full_inner( @@ -558,7 +571,11 @@ fn document_full_inner( cx: &Context<'_>, is_collapsible: bool, heading_offset: HeadingOffset, + extra: Option, ) { + if let Some(extra) = extra { + w.write_str(&extra); + } if let Some(s) = item.collapsed_doc_value() { debug!("Doc block: =====\n{}\n=====", s); if is_collapsible { @@ -1442,7 +1459,7 @@ fn render_impl( // because impls can't have a stability. if item.doc_value().is_some() { document_item_info(&mut info_buffer, cx, it, Some(parent)); - document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); + document_full(&mut doc_buffer, item, cx, HeadingOffset::H5, None); short_documented = false; } else { // In case the item isn't documented, @@ -1460,7 +1477,7 @@ fn render_impl( } else { document_item_info(&mut info_buffer, cx, item, Some(parent)); if rendering_params.show_def_docs { - document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); + document_full(&mut doc_buffer, item, cx, HeadingOffset::H5, None); short_documented = false; } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 1ed5c662c41cc..04e79753f8557 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -16,7 +16,7 @@ use rustc_span::symbol::{kw, sym, Symbol}; use rustc_target::abi::{Layout, Primitive, TagEncoding, Variants}; use super::{ - collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_section, + collect_paths_for_type, document, document_inner, ensure_trailing_slash, item_ty_to_section, notable_traits_decl, render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre, render_impl, render_stability_since_raw, write_srclink, AssocItemLink, Context, ImplRenderingParameters, @@ -188,7 +188,12 @@ fn toggle_close(w: &mut Buffer) { } fn item_module(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) { - document(w, cx, item, None, HeadingOffset::H2); + if item.is_crate() { + let extra = cx.shared.minimum_supported_rust_version.as_ref().map(|v| format!("
ⓘ The minimum supported Rust version for this crate is: {}.
", v)); + document_inner(w, cx, item, None, HeadingOffset::H2, extra); + } else { + document(w, cx, item, None, HeadingOffset::H2); + } let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::>(); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 68c88b551ca74..7221a4e290170 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -581,6 +581,11 @@ h2.location a { cursor: pointer; } +.extra-info { + margin-bottom: 1.5rem; + font-size: 1.05rem; +} + .docblock-short { overflow-wrap: break-word; overflow-wrap: anywhere; diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 0fcfabba4c0a1..9e3cb9180dd2c 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -611,6 +611,14 @@ fn opts() -> Vec { "path to function call information (for displaying examples in the documentation)", ) }), + unstable("minimum-supported-rust-version", |o| { + o.optopt( + "", + "minimum-supported-rust-version", + "if provided, it will display the minimum supported rust version in the documentation", + "VERSION", + ) + }), // deprecated / removed options stable("plugin-path", |o| { o.optmulti( diff --git a/src/test/rustdoc-ui/minimum-supported-rust-version.rs b/src/test/rustdoc-ui/minimum-supported-rust-version.rs new file mode 100644 index 0000000000000..0b7f3ddd60781 --- /dev/null +++ b/src/test/rustdoc-ui/minimum-supported-rust-version.rs @@ -0,0 +1,3 @@ +// compile-flags:-Zunstable-options --minimum-supported-rust-version 1.48 + +pub struct Foo; diff --git a/src/test/rustdoc-ui/minimum-supported-rust-version.stderr b/src/test/rustdoc-ui/minimum-supported-rust-version.stderr new file mode 100644 index 0000000000000..722d35caf1da3 --- /dev/null +++ b/src/test/rustdoc-ui/minimum-supported-rust-version.stderr @@ -0,0 +1,2 @@ +error: --minimum-supported-rust-version expects a valid semver value: unexpected end of input while parsing minor version number + diff --git a/src/test/rustdoc/minimum-supported-rust-version.rs b/src/test/rustdoc/minimum-supported-rust-version.rs new file mode 100644 index 0000000000000..b9c2e47537aa0 --- /dev/null +++ b/src/test/rustdoc/minimum-supported-rust-version.rs @@ -0,0 +1,18 @@ +// compile-flags:-Zunstable-options --minimum-supported-rust-version 1.48.0 + +#![crate_name = "foo"] + +// @has 'foo/index.html' +// @has - '//*[@id="main-content"]/*[@class="extra-info"]' 'ⓘ The minimum supported Rust version for this crate is: 1.48.0.' +// @has - '//*[@class="rustdoc-toggle top-doc"]/*[@class="docblock"]' 'This crate is awesome.' + +//! This crate is awesome. + +// We check that the minimum supported rust version is only on the crate page. +// @has 'foo/struct.Foo.html' +// @!has - '//*[@class="extra-info"]' +pub struct Foo; + +// @has 'foo/bar/index.html' +// @!has - '//*[@class="extra-info"]' +pub mod bar {}