| use std::collections::BTreeMap; | |
| use std::ffi::OsStr; | |
| use std::io::Read; | |
| use std::path::{Path, PathBuf}; | |
| use std::str::FromStr; | |
| use std::{fmt, io}; | |
| use rustc_data_structures::fx::FxIndexMap; | |
| use rustc_errors::DiagCtxtHandle; | |
| use rustc_lint::Level; | |
| use rustc_session::config::{ | |
| self, CodegenOptions, ErrorOutputType, Externs, Input, JsonUnusedExterns, | |
| OptionsTargetModifiers, OutFileName, PrintCategory, PrintRequest, Sysroot, UnstableOptions, | |
| collect_print_requests, get_cmd_lint_options, nightly_options, parse_crate_types_from_list, | |
| parse_externs, parse_target_triple, | |
| }; | |
| use rustc_session::search_paths::SearchPath; | |
| use rustc_session::{EarlyDiagCtxt, getopts}; | |
| use rustc_span::edition::Edition; | |
| use rustc_span::{FileName, RemapPathScopeComponents}; | |
| use rustc_structures::CrateType; | |
| use rustc_target::spec::TargetTuple; | |
| use smallvec::SmallVec; | |
| use crate::core::new_dcx; | |
| use crate::externalfiles::ExternalHtml; | |
| use crate::html::markdown::IdMap; | |
| use crate::html::render::StylePath; | |
| use crate::html::static_files; | |
| use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions}; | |
| use crate::{html, opts, theme}; | |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] | |
| pub(crate) enum OutputFormat { | |
| /// `--output-format=json` without `--show-coverage`. | |
| /// | |
| /// JSON description of crate API. | |
| IrJson, | |
| /// `--output-format=json` with `--show-coverage`. | |
| CoverageJson, | |
| Html, | |
| Doctest, | |
| } | |
| /// Either an input crate, markdown file, or nothing (--merge=finalize). | |
| pub(crate) enum InputMode { | |
| /// The `--merge=finalize` step does not need an input crate to rustdoc. | |
| NoInputMergeFinalize, | |
| /// A crate or markdown file. | |
| HasFile(Input), | |
| } | |
| /// Whether to run multiple doctests in the same binary. | |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] | |
| pub(crate) enum MergeDoctests { | |
| #[default] | |
| Never, | |
| Always, | |
| Auto, | |
| } | |
| /// Configuration options for rustdoc. | |
| #[derive(Clone)] | |
| pub(crate) struct Options { | |
| // Basic options / Options passed directly to rustc | |
| /// The name of the crate being documented. | |
| pub(crate) crate_name: Option<String>, | |
| /// Whether or not this is a bin crate | |
| pub(crate) bin_crate: bool, | |
| /// Whether or not this is a proc-macro crate | |
| pub(crate) proc_macro_crate: bool, | |
| /// How to format errors and warnings. | |
| pub(crate) error_format: ErrorOutputType, | |
| /// Width of output buffer to truncate errors appropriately. | |
| pub(crate) diagnostic_width: Option<usize>, | |
| /// Library search paths to hand to the compiler. | |
| pub(crate) libs: Vec<SearchPath>, | |
| /// Library search paths strings to hand to the compiler. | |
| pub(crate) lib_strs: Vec<String>, | |
| /// The list of external crates to link against. | |
| pub(crate) externs: Externs, | |
| /// The list of external crates strings to link against. | |
| pub(crate) extern_strs: Vec<String>, | |
| /// List of `cfg` flags to hand to the compiler. Always includes `rustdoc`. | |
| pub(crate) cfgs: Vec<String>, | |
| /// List of check cfg flags to hand to the compiler. | |
| pub(crate) check_cfgs: Vec<String>, | |
| /// Codegen options to hand to the compiler. | |
| pub(crate) codegen_options: CodegenOptions, | |
| /// Codegen options strings to hand to the compiler. | |
| pub(crate) codegen_options_strs: Vec<String>, | |
| /// Unstable (`-Z`) options to pass to the compiler. | |
| pub(crate) unstable_opts: UnstableOptions, | |
| /// Unstable (`-Z`) options strings to pass to the compiler. | |
| pub(crate) unstable_opts_strs: Vec<String>, | |
| /// The target used to compile the crate against. | |
| pub(crate) target: TargetTuple, | |
| /// Edition used when reading the crate. Defaults to "2015". Also used by default when | |
| /// compiling doctests from the crate. | |
| pub(crate) edition: Edition, | |
| /// The path to the sysroot. Used during the compilation process. | |
| pub(crate) sysroot: Sysroot, | |
| /// Lint information passed over the command-line. | |
| pub(crate) lint_opts: Vec<(String, Level)>, | |
| /// Whether to ask rustc to describe the lints it knows. | |
| pub(crate) describe_lints: bool, | |
| /// What level to cap lints at. | |
| pub(crate) lint_cap: Option<Level>, | |
| /// Print requests to hand to the compiler. | |
| pub(crate) prints: Vec<PrintRequest>, | |
| // Options specific to running doctests | |
| /// Whether we should run doctests instead of generating docs. | |
| pub(crate) should_test: bool, | |
| /// List of arguments to pass to the test harness, if running tests. | |
| pub(crate) test_args: Vec<String>, | |
| /// The working directory in which to run tests. | |
| pub(crate) test_run_directory: Option<PathBuf>, | |
| /// Optional path to persist the doctest executables to, defaults to a | |
| /// temporary directory if not set. | |
| pub(crate) persist_doctests: Option<PathBuf>, | |
| /// Whether to merge | |
| pub(crate) merge_doctests: MergeDoctests, | |
| /// Runtool to run doctests with | |
| pub(crate) test_runtool: Option<String>, | |
| /// Arguments to pass to the runtool | |
| pub(crate) test_runtool_args: Vec<String>, | |
| /// Do not run doctests, compile them if should_test is active. | |
| pub(crate) no_run: bool, | |
| /// What sources are being mapped. | |
| pub(crate) remap_path_prefix: Vec<(PathBuf, PathBuf)>, | |
| /// Which scope(s) to use with `--remap-path-prefix` | |
| pub(crate) remap_path_scope: RemapPathScopeComponents, | |
| /// The path to a rustc-like binary to build tests with. If not set, we | |
| /// default to loading from `$sysroot/bin/rustc`. | |
| pub(crate) test_builder: Option<PathBuf>, | |
| /// Run these wrapper instead of rustc directly | |
| pub(crate) test_builder_wrappers: Vec<PathBuf>, | |
| // Options that affect the documentation process | |
| /// Whether to run the `calculate-doc-coverage` pass, which counts the number of public items | |
| /// with and without documentation. | |
| pub(crate) show_coverage: bool, | |
| // Options that alter generated documentation pages | |
| /// Crate version to note on the sidebar of generated docs. | |
| pub(crate) crate_version: Option<String>, | |
| /// The format that we output when rendering. | |
| /// | |
| /// Currently used only for the `--show-coverage` option. | |
| pub(crate) output_format: OutputFormat, | |
| /// If this option is set to `true`, rustdoc will only run checks and not generate | |
| /// documentation. | |
| pub(crate) run_check: bool, | |
| /// Whether doctests should emit unused externs | |
| pub(crate) json_unused_externs: JsonUnusedExterns, | |
| /// Whether to skip capturing stdout and stderr of tests. | |
| pub(crate) no_capture: bool, | |
| /// Configuration for scraping examples from the current crate. If this option is Some(..) then | |
| /// the compiler will scrape examples and not generate documentation. | |
| pub(crate) scrape_examples_options: Option<ScrapeExamplesOptions>, | |
| /// Note: this field is duplicated in `RenderOptions` because it's useful | |
| /// to have it in both places. | |
| pub(crate) unstable_features: rustc_feature::UnstableFeatures, | |
| /// Arguments to be used when compiling doctests. | |
| pub(crate) doctest_build_args: Vec<String>, | |
| /// Target modifiers. | |
| pub(crate) target_modifiers: BTreeMap<OptionsTargetModifiers, String>, | |
| } | |
| impl fmt::Debug for Options { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| struct FmtExterns<'a>(&'a Externs); | |
| impl fmt::Debug for FmtExterns<'_> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.debug_map().entries(self.0.iter()).finish() | |
| } | |
| } | |
| f.debug_struct("Options") | |
| .field("crate_name", &self.crate_name) | |
| .field("bin_crate", &self.bin_crate) | |
| .field("proc_macro_crate", &self.proc_macro_crate) | |
| .field("error_format", &self.error_format) | |
| .field("libs", &self.libs) | |
| .field("externs", &FmtExterns(&self.externs)) | |
| .field("cfgs", &self.cfgs) | |
| .field("check-cfgs", &self.check_cfgs) | |
| .field("codegen_options", &"...") | |
| .field("unstable_options", &"...") | |
| .field("target", &self.target) | |
| .field("edition", &self.edition) | |
| .field("sysroot", &self.sysroot) | |
| .field("lint_opts", &self.lint_opts) | |
| .field("describe_lints", &self.describe_lints) | |
| .field("lint_cap", &self.lint_cap) | |
| .field("prints", &self.prints) | |
| .field("should_test", &self.should_test) | |
| .field("test_args", &self.test_args) | |
| .field("test_run_directory", &self.test_run_directory) | |
| .field("persist_doctests", &self.persist_doctests) | |
| .field("show_coverage", &self.show_coverage) | |
| .field("crate_version", &self.crate_version) | |
| .field("test_runtool", &self.test_runtool) | |
| .field("test_runtool_args", &self.test_runtool_args) | |
| .field("run_check", &self.run_check) | |
| .field("no_run", &self.no_run) | |
| .field("test_builder_wrappers", &self.test_builder_wrappers) | |
| .field("remap-file-prefix", &self.remap_path_prefix) | |
| .field("remap-file-scope", &self.remap_path_scope) | |
| .field("no_capture", &self.no_capture) | |
| .field("scrape_examples_options", &self.scrape_examples_options) | |
| .field("unstable_features", &self.unstable_features) | |
| .finish() | |
| } | |
| } | |
| /// Configuration options for the HTML page-creation process. | |
| #[derive(Clone, Debug)] | |
| pub(crate) struct RenderOptions { | |
| /// Output directory to generate docs into. Defaults to `doc`. | |
| pub(crate) output: PathBuf, | |
| /// External files to insert into generated pages. | |
| pub(crate) external_html: ExternalHtml, | |
| /// A pre-populated `IdMap` with the default headings and any headings added by Markdown files | |
| /// processed by `external_html`. | |
| pub(crate) id_map: IdMap, | |
| /// If present, playground URL to use in the "Run" button added to code samples. | |
| /// | |
| /// Be aware: This option can come both from the CLI and from crate attributes! | |
| pub(crate) playground_url: Option<String>, | |
| /// What sorting mode to use for module pages. | |
| /// `ModuleSorting::Alphabetical` by default. | |
| pub(crate) module_sorting: ModuleSorting, | |
| /// List of themes to extend the docs with. Original argument name is included to assist in | |
| /// displaying errors if it fails a theme check. | |
| pub(crate) themes: Vec<StylePath>, | |
| /// If present, CSS file that contains rules to add to the default CSS. | |
| pub(crate) extension_css: Option<PathBuf>, | |
| /// A map of crate names to the URL to use instead of querying the crate's `html_root_url`. | |
| pub(crate) extern_html_root_urls: BTreeMap<String, String>, | |
| /// Whether to give precedence to `html_root_url` or `--extern-html-root-url`. | |
| pub(crate) extern_html_root_takes_precedence: bool, | |
| /// A map of the default settings (values are as for DOM storage API). Keys should lack the | |
| /// `rustdoc-` prefix. | |
| pub(crate) default_settings: FxIndexMap<String, String>, | |
| /// If present, suffix added to CSS/JavaScript files when referencing them in generated pages. | |
| pub(crate) resource_suffix: String, | |
| /// Whether to create an index page in the root of the output directory. If this is true but | |
| /// `enable_index_page` is None, generate a static listing of crates instead. | |
| pub(crate) enable_index_page: bool, | |
| /// A file to use as the index page at the root of the output directory. Overrides | |
| /// `enable_index_page` to be true if set. | |
| pub(crate) index_page: Option<PathBuf>, | |
| /// An optional path to use as the location of static files. If not set, uses combinations of | |
| /// `../` to reach the documentation root. | |
| pub(crate) static_root_path: Option<String>, | |
| // Options specific to reading standalone Markdown files | |
| /// Whether to generate a table of contents on the output file when reading a standalone | |
| /// Markdown file. | |
| pub(crate) markdown_no_toc: bool, | |
| /// Additional CSS files to link in pages generated from standalone Markdown files. | |
| pub(crate) markdown_css: Vec<String>, | |
| /// If present, playground URL to use in the "Run" button added to code samples generated from | |
| /// standalone Markdown files. If not present, `playground_url` is used. | |
| pub(crate) markdown_playground_url: Option<String>, | |
| /// Document items that have lower than `pub` visibility. | |
| pub(crate) document_private: bool, | |
| /// Document items that have `doc(hidden)`. | |
| pub(crate) document_hidden: bool, | |
| /// If `true`, generate a JSON file in the crate folder instead of HTML redirection files. | |
| pub(crate) generate_redirect_map: bool, | |
| /// Show the memory layout of types in the docs. | |
| pub(crate) show_type_layout: bool, | |
| /// Note: this field is duplicated in `Options` because it's useful to have | |
| /// it in both places. | |
| pub(crate) unstable_features: rustc_feature::UnstableFeatures, | |
| pub(crate) emit: SmallVec<[EmitType; 2]>, | |
| /// If `true`, HTML source pages will generate links for items to their definition. | |
| pub(crate) generate_link_to_definition: bool, | |
| /// Set of function-call locations to include as examples | |
| pub(crate) call_locations: AllCallLocations, | |
| /// If `true`, Context::init will not emit shared files. | |
| pub(crate) no_emit_shared: bool, | |
| /// If `true`, HTML source code pages won't be generated. | |
| pub(crate) html_no_source: bool, | |
| /// This field is only used for the JSON output. If it's set to true, no file will be created | |
| /// and content will be displayed in stdout directly. | |
| pub(crate) output_to_stdout: bool, | |
| /// Whether we should read or write rendered cross-crate info in the doc root. | |
| pub(crate) should_merge: ShouldMerge, | |
| /// Path to crate-info for external crates. | |
| pub(crate) include_parts_dir: Vec<PathToParts>, | |
| /// Where to write crate-info | |
| pub(crate) parts_out_dir: Option<PathToParts>, | |
| /// disable minification of CSS/JS | |
| pub(crate) disable_minification: bool, | |
| /// If `true`, HTML source pages will generate the possibility to expand macros. | |
| pub(crate) generate_macro_expansion: bool, | |
| } | |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] | |
| pub(crate) enum ModuleSorting { | |
| DeclarationOrder, | |
| Alphabetical, | |
| } | |
| #[derive(Clone, Debug, PartialEq, Eq)] | |
| pub(crate) enum EmitType { | |
| HtmlStaticFiles, | |
| HtmlNonStaticFiles, | |
| // not explicitly nameable by the user for now | |
| IrJsonFiles, | |
| CoverageJsonFiles, | |
| DepInfo(Option<OutFileName>), | |
| } | |
| impl fmt::Display for EmitType { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.write_str(match self { | |
| Self::HtmlStaticFiles => "html-static-files", | |
| Self::HtmlNonStaticFiles => "html-non-static-files", | |
| Self::IrJsonFiles => "ir-json-files", | |
| Self::CoverageJsonFiles => "coverage-json-files", | |
| Self::DepInfo(_) => "dep-info", | |
| }) | |
| } | |
| } | |
| impl FromStr for EmitType { | |
| type Err = (); | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| match s { | |
| "html-static-files" => Ok(Self::HtmlStaticFiles), | |
| "html-non-static-files" => Ok(Self::HtmlNonStaticFiles), | |
| "dep-info" => Ok(Self::DepInfo(None)), | |
| option => match option.strip_prefix("dep-info=") { | |
| Some("-") => Ok(Self::DepInfo(Some(OutFileName::Stdout))), | |
| Some(f) => Ok(Self::DepInfo(Some(OutFileName::Real(f.into())))), | |
| None => Err(()), | |
| }, | |
| } | |
| } | |
| } | |
| impl RenderOptions { | |
| pub(crate) fn dep_info(&self) -> Option<Option<&OutFileName>> { | |
| self.emit.iter().find_map(|emit| match emit { | |
| EmitType::DepInfo(file) => Some(file.as_ref()), | |
| _ => None, | |
| }) | |
| } | |
| } | |
| /// Create the input (string or file path) | |
| /// | |
| /// Warning: Return an unrecoverable error in case of error! | |
| fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input { | |
| if input == "-" { | |
| let mut src = String::new(); | |
| if io::stdin().read_to_string(&mut src).is_err() { | |
| // Immediately stop compilation if there was an issue reading | |
| // the input (for example if the input stream is not UTF-8). | |
| early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8"); | |
| } | |
| Input::Str { name: FileName::anon_source_code(&src), input: src } | |
| } else { | |
| Input::File(PathBuf::from(input)) | |
| } | |
| } | |
| impl Options { | |
| /// Parses the given command-line for options. If an error message or other early-return has | |
| /// been printed, returns `Err` with the exit code. | |
| pub(crate) fn from_matches( | |
| early_dcx: &mut EarlyDiagCtxt, | |
| matches: &getopts::Matches, | |
| args: Vec<String>, | |
| ) -> Option<(InputMode, Options, RenderOptions, Vec<PathBuf>)> { | |
| // Check for unstable options. | |
| nightly_options::check_nightly_options(early_dcx, matches, &opts()); | |
| if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") { | |
| crate::usage("rustdoc"); | |
| return None; | |
| } else if matches.opt_present("version") { | |
| rustc_driver::version!(&early_dcx, "rustdoc", matches); | |
| return None; | |
| } | |
| if rustc_driver::describe_flag_categories(early_dcx, matches) { | |
| return None; | |
| } | |
| let color = config::parse_color(early_dcx, matches); | |
| let crate_name = matches.opt_str("crate-name"); | |
| let unstable_features = | |
| rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); | |
| let config::JsonConfig { json_rendered, json_unused_externs, json_color, .. } = | |
| config::parse_json(early_dcx, matches); | |
| let error_format = | |
| config::parse_error_format(early_dcx, matches, color, json_color, json_rendered); | |
| let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default(); | |
| let mut collected_options = Default::default(); | |
| let mut codegen_options = CodegenOptions::build(early_dcx, matches, &mut collected_options); | |
| let unstable_opts = UnstableOptions::build(early_dcx, matches, &mut collected_options); | |
| let remap_path_prefix = match parse_remap_path_prefix(matches) { | |
| Ok(prefix_mappings) => prefix_mappings, | |
| Err(err) => { | |
| early_dcx.early_fatal(err); | |
| } | |
| }; | |
| let remap_path_scope = | |
| rustc_session::config::parse_remap_path_scope(early_dcx, matches, &unstable_opts); | |
| let dcx = new_dcx(error_format, None, diagnostic_width, &unstable_opts); | |
| let dcx = dcx.handle(); | |
| // check for deprecated options | |
| check_deprecated_options(matches, dcx); | |
| let should_test = matches.opt_present("test"); | |
| let show_coverage = matches.opt_present("show-coverage"); | |
| let output_format_s = matches.opt_str("output-format"); | |
| let output_format = match output_format_s.as_deref() { | |
| None | Some("html") => OutputFormat::Html, | |
| Some("json") => { | |
| if show_coverage { | |
| OutputFormat::CoverageJson | |
| } else { | |
| OutputFormat::IrJson | |
| } | |
| } | |
| Some("doctest") => OutputFormat::Doctest, | |
| Some(other) => dcx.fatal(format!("unknown output format `{other}`")), | |
| }; | |
| // check for `--output-format` stability, and compatibility with `--show-coverage` | |
| match ( | |
| output_format_s.as_ref().map(|_| output_format), | |
| show_coverage, | |
| nightly_options::is_unstable_enabled(matches), | |
| ) { | |
| (None | Some(OutputFormat::CoverageJson), true, _) => {} | |
| (_, true, _) => { | |
| dcx.fatal(format!( | |
| "`--output-format={}` is not supported for the `--show-coverage` option", | |
| output_format_s.expect("checked for none above"), | |
| )); | |
| } | |
| // If `-Zunstable-options` is used, nothing to check after this point. | |
| (_, false, true) => {} | |
| (None | Some(OutputFormat::Html), false, _) => {} | |
| (Some(OutputFormat::IrJson), false, false) => { | |
| dcx.fatal( | |
| "the -Z unstable-options flag must be passed to enable --output-format=json for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", | |
| ); | |
| } | |
| (Some(OutputFormat::Doctest), false, false) => { | |
| dcx.fatal( | |
| "the -Z unstable-options flag must be passed to enable --output-format=doctest (see https://github.com/rust-lang/rust/issues/134529)", | |
| ); | |
| } | |
| (Some(OutputFormat::CoverageJson), false, _) => { | |
| unreachable!("CoverageJson is only possible when show_coverage is true") | |
| } | |
| } | |
| let mut emit = FxIndexMap::default(); | |
| for list in matches.opt_strs("emit") { | |
| if should_test { | |
| dcx.fatal("the `--test` flag and the `--emit` flag are not supported together"); | |
| } | |
| if let OutputFormat::Doctest = output_format { | |
| dcx.fatal("the `--emit` flag is not supported with `--output-format=doctest`"); | |
| } | |
| for typ in list.split(',') { | |
| let Ok(typ) = typ.parse::<EmitType>() else { | |
| dcx.fatal(format!("unrecognized emission type: {typ}")) | |
| }; | |
| match typ { | |
| EmitType::DepInfo(_) => match output_format { | |
| OutputFormat::Html | OutputFormat::IrJson | OutputFormat::CoverageJson => {} | |
| OutputFormat::Doctest => unreachable!(), | |
| }, | |
| EmitType::HtmlStaticFiles | EmitType::HtmlNonStaticFiles => match output_format | |
| { | |
| OutputFormat::Html => {} | |
| OutputFormat::IrJson | OutputFormat::CoverageJson => dcx.fatal(format!( | |
| "the `--emit={typ}` flag is not supported with `--output-format=json`", | |
| )), | |
| OutputFormat::Doctest => unreachable!(), | |
| }, | |
| EmitType::IrJsonFiles | EmitType::CoverageJsonFiles => unreachable!(), | |
| } | |
| // De-duplicate emit types and the last wins. | |
| // Only one instance for each type is allowed | |
| // regardless the actual data it carries. | |
| // This matches rustc's `--emit` behavior. | |
| emit.insert(std::mem::discriminant(&typ), typ); | |
| } | |
| } | |
| let mut emit: SmallVec<[_; 2]> = emit.into_values().collect(); | |
| // If `--emit` is absent we'll register default emission types depending on the requested | |
| // output format. We can safely use `is_empty` for this since `--emit=` ("truly empty") | |
| // will have already been rejected above. | |
| if emit.is_empty() { | |
| match output_format { | |
| OutputFormat::IrJson => emit.push(EmitType::IrJsonFiles), | |
| OutputFormat::CoverageJson => emit.push(EmitType::CoverageJsonFiles), | |
| OutputFormat::Html => { | |
| emit.push(EmitType::HtmlStaticFiles); | |
| emit.push(EmitType::HtmlNonStaticFiles); | |
| } | |
| OutputFormat::Doctest => {} | |
| } | |
| } | |
| let to_check = matches.opt_strs("check-theme"); | |
| if !to_check.is_empty() { | |
| let mut content = | |
| std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap(); | |
| if let Some((_, inside)) = content.split_once("/* Begin theme: light */") { | |
| content = inside; | |
| } | |
| if let Some((inside, _)) = content.split_once("/* End theme: light */") { | |
| content = inside; | |
| } | |
| let paths = match theme::load_css_paths(content) { | |
| Ok(p) => p, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| let mut errors = 0; | |
| println!("rustdoc: [check-theme] Starting tests! (Ignoring all other arguments)"); | |
| for theme_file in to_check.iter() { | |
| print!(" - Checking \"{theme_file}\"..."); | |
| let (success, differences) = theme::test_theme_against(theme_file, &paths, dcx); | |
| if !differences.is_empty() || !success { | |
| println!(" FAILED"); | |
| errors += 1; | |
| if !differences.is_empty() { | |
| println!("{}", differences.join("\n")); | |
| } | |
| } else { | |
| println!(" OK"); | |
| } | |
| } | |
| if errors != 0 { | |
| dcx.fatal("[check-theme] one or more tests failed"); | |
| } | |
| return None; | |
| } | |
| let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); | |
| let externs = parse_externs(early_dcx, matches, &unstable_opts); | |
| let extern_html_root_urls = match parse_extern_html_roots(matches) { | |
| Ok(ex) => ex, | |
| Err(err) => dcx.fatal(err), | |
| }; | |
| let prints = collect_print_requests( | |
| early_dcx, | |
| &mut codegen_options, | |
| &unstable_opts, | |
| matches, | |
| &[PrintCategory::Target, PrintCategory::Crate], | |
| ); | |
| let mut parts_out_dir = | |
| match matches.opt_str("write-doc-meta-dir").map(PathToParts::from_flag).transpose() { | |
| Ok(parts_out_dir) => parts_out_dir, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| let mut include_parts_dir = match parse_read_doc_meta(matches, "read-doc-meta-dir") { | |
| Ok(include_parts_dir) => include_parts_dir, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| let mut should_merge = match compute_should_merge(matches) { | |
| Ok(should_merge) => should_merge, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| if parts_out_dir.is_none() && include_parts_dir.is_empty() { | |
| // we'll need to get rid of this stuff once Cargo stops using them | |
| parts_out_dir = | |
| match matches.opt_str("parts-out-dir").map(PathToParts::from_flag).transpose() { | |
| Ok(parts_out_dir) => parts_out_dir, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| include_parts_dir = match parse_read_doc_meta(matches, "include-parts-dir") { | |
| Ok(include_parts_dir) => include_parts_dir, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| should_merge = match matches.opt_str("merge").as_deref() { | |
| None => ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }, | |
| Some("none") => ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }, | |
| Some("shared") => ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }, | |
| Some("finalize") => { | |
| ShouldMerge { read_rendered_cci: false, write_rendered_cci: true } | |
| } | |
| Some(_) => dcx.fatal("argument to --merge must be `none`, `shared`, or `finalize`"), | |
| }; | |
| } else if matches.opt_str("parts-out-dir").is_some() { | |
| dcx.fatal( | |
| "deprecated version of write-doc-meta-dir is used with new doc-meta-dir stuff", | |
| ); | |
| } else if matches.opt_str("include-parts-dir").is_some() { | |
| dcx.fatal( | |
| "deprecated version of read-doc-meta-dir is used with new doc-meta-dir stuff", | |
| ); | |
| } else if matches.opt_str("merge").is_some() { | |
| dcx.fatal("deprecated parameter merge is used with new doc-meta-dir stuff"); | |
| } | |
| let input = if describe_lints { | |
| InputMode::HasFile(make_input(early_dcx, "")) | |
| } else { | |
| match matches.free.as_slice() { | |
| [] if !include_parts_dir.is_empty() && should_merge.write_rendered_cci => { | |
| InputMode::NoInputMergeFinalize | |
| } | |
| [] => dcx.fatal("missing file operand"), | |
| [input] => InputMode::HasFile(make_input(early_dcx, input)), | |
| _ => dcx.fatal("too many file operands"), | |
| } | |
| }; | |
| let default_settings: Vec<Vec<(String, String)>> = vec![ | |
| matches | |
| .opt_str("default-theme") | |
| .iter() | |
| .flat_map(|theme| { | |
| vec![ | |
| ("use-system-theme".to_string(), "false".to_string()), | |
| ("theme".to_string(), theme.to_string()), | |
| ] | |
| }) | |
| .collect(), | |
| matches | |
| .opt_strs("default-setting") | |
| .iter() | |
| .map(|s| match s.split_once('=') { | |
| None => (s.clone(), "true".to_string()), | |
| Some((k, v)) => (k.to_string(), v.to_string()), | |
| }) | |
| .collect(), | |
| ]; | |
| let default_settings = default_settings | |
| .into_iter() | |
| .flatten() | |
| .map( | |
| // The keys here become part of `data-` attribute names in the generated HTML. The | |
| // browser does a strange mapping when converting them into attributes on the | |
| // `dataset` property on the DOM HTML Node: | |
| // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | |
| // | |
| // The original key values we have are the same as the DOM storage API keys and the | |
| // command line options, so contain `-`. Our JavaScript needs to be able to look | |
| // these values up both in `dataset` and in the storage API, so it needs to be able | |
| // to convert the names back and forth. Despite doing this kebab-case to | |
| // StudlyCaps transformation automatically, the JS DOM API does not provide a | |
| // mechanism for doing just the transformation on a string. So we want to avoid | |
| // the StudlyCaps representation in the `dataset` property. | |
| // | |
| // We solve this by replacing all the `-`s with `_`s. We do that here, when we | |
| // generate the `data-` attributes, and in the JS, when we look them up. (See | |
| // `getSettingValue` in `storage.js.`) Converting `-` to `_` is simple in JS. | |
| // | |
| // The values will be HTML-escaped by the default Tera escaping. | |
| |(k, v)| (k.replace('-', "_"), v), | |
| ) | |
| .collect(); | |
| let test_args = matches.opt_strs("test-args"); | |
| let test_args: Vec<String> = | |
| test_args.iter().flat_map(|s| s.split_whitespace()).map(|s| s.to_string()).collect(); | |
| let no_run = matches.opt_present("no-run"); | |
| if !should_test && no_run { | |
| dcx.fatal("the `--test` flag must be passed to enable `--no-run`"); | |
| } | |
| let mut output_to_stdout = false; | |
| let test_builder_wrappers = | |
| matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect(); | |
| let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) { | |
| (Some(_), Some(_)) => { | |
| dcx.fatal("cannot use both 'out-dir' and 'output' at once"); | |
| } | |
| (Some(out_dir), None) | (None, Some(out_dir)) => { | |
| output_to_stdout = out_dir == "-"; | |
| PathBuf::from(out_dir) | |
| } | |
| (None, None) => { | |
| if show_coverage { | |
| // If no `-o` option is given and we're in the `--show-coverage` mode, by | |
| // default we print on the stdout. | |
| output_to_stdout = true; | |
| } | |
| PathBuf::from("doc") | |
| } | |
| }; | |
| let cfgs = matches.opt_strs("cfg"); | |
| let check_cfgs = matches.opt_strs("check-cfg"); | |
| let extension_css = matches.opt_str("e").map(|s| PathBuf::from(&s)); | |
| let mut loaded_paths = Vec::new(); | |
| if let Some(ref p) = extension_css { | |
| loaded_paths.push(p.clone()); | |
| if !p.is_file() { | |
| dcx.fatal("option --extend-css argument must be a file"); | |
| } | |
| } | |
| let mut themes = Vec::new(); | |
| if matches.opt_present("theme") { | |
| let mut content = | |
| std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap(); | |
| if let Some((_, inside)) = content.split_once("/* Begin theme: light */") { | |
| content = inside; | |
| } | |
| if let Some((inside, _)) = content.split_once("/* End theme: light */") { | |
| content = inside; | |
| } | |
| let paths = match theme::load_css_paths(content) { | |
| Ok(p) => p, | |
| Err(e) => dcx.fatal(e), | |
| }; | |
| for (theme_file, theme_s) in | |
| matches.opt_strs("theme").iter().map(|s| (PathBuf::from(&s), s.to_owned())) | |
| { | |
| if !theme_file.is_file() { | |
| dcx.struct_fatal(format!("invalid argument: \"{theme_s}\"")) | |
| .with_help("arguments to --theme must be files") | |
| .emit_fatal(); | |
| } | |
| if theme_file.extension() != Some(OsStr::new("css")) { | |
| dcx.struct_fatal(format!("invalid argument: \"{theme_s}\"")) | |
| .with_help("arguments to --theme must have a .css extension") | |
| .emit_fatal(); | |
| } | |
| let (success, ret) = theme::test_theme_against(&theme_file, &paths, dcx); | |
| if !success { | |
| dcx.fatal(format!("error loading theme file: \"{theme_s}\"")); | |
| } else if !ret.is_empty() { | |
| dcx.struct_warn(format!( | |
| "theme file \"{theme_s}\" is missing CSS rules from the default theme", | |
| )) | |
| .with_warn("the theme may appear incorrect when loaded") | |
| .with_help(format!( | |
| "to see what rules are missing, call `rustdoc --check-theme \"{theme_s}\"`", | |
| )) | |
| .emit(); | |
| } | |
| loaded_paths.push(theme_file.clone()); | |
| themes.push(StylePath { path: theme_file }); | |
| } | |
| } | |
| let edition = config::parse_crate_edition(early_dcx, matches); | |
| let mut id_map = html::markdown::IdMap::new(); | |
| let Some(external_html) = ExternalHtml::load( | |
| &matches.opt_strs("html-in-header"), | |
| &matches.opt_strs("html-before-content"), | |
| &matches.opt_strs("html-after-content"), | |
| &matches.opt_strs("markdown-before-content"), | |
| &matches.opt_strs("markdown-after-content"), | |
| nightly_options::match_is_nightly_build(matches), | |
| dcx, | |
| &mut id_map, | |
| edition, | |
| &None, | |
| &mut loaded_paths, | |
| ) else { | |
| dcx.fatal("`ExternalHtml::load` failed"); | |
| }; | |
| match matches.opt_str("r").as_deref() { | |
| Some("rust") | None => {} | |
| Some(s) => dcx.fatal(format!("unknown input format: {s}")), | |
| } | |
| let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s)); | |
| 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); | |
| let sysroot = Sysroot::new(matches.opt_str("sysroot").map(PathBuf::from)); | |
| let libs = matches | |
| .opt_strs("L") | |
| .iter() | |
| .map(|s| { | |
| SearchPath::from_cli_opt( | |
| sysroot.path(), | |
| &target, | |
| early_dcx, | |
| s, | |
| #[allow(rustc::bad_opt_access)] // we have no `Session` here | |
| unstable_opts.unstable_options, | |
| ) | |
| }) | |
| .collect(); | |
| let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) { | |
| Ok(types) => types, | |
| Err(e) => { | |
| dcx.fatal(format!("unknown crate type: {e}")); | |
| } | |
| }; | |
| let bin_crate = crate_types.contains(&CrateType::Executable); | |
| let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); | |
| let playground_url = matches.opt_str("playground-url"); | |
| let module_sorting = if matches.opt_present("sort-modules-by-appearance") { | |
| ModuleSorting::DeclarationOrder | |
| } else { | |
| ModuleSorting::Alphabetical | |
| }; | |
| let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default(); | |
| let markdown_no_toc = matches.opt_present("markdown-no-toc"); | |
| let markdown_css = matches.opt_strs("markdown-css"); | |
| let markdown_playground_url = matches.opt_str("markdown-playground-url"); | |
| let crate_version = matches.opt_str("crate-version"); | |
| let enable_index_page = matches.opt_present("enable-index-page") || index_page.is_some(); | |
| let static_root_path = matches.opt_str("static-root-path"); | |
| let test_run_directory = matches.opt_str("test-run-directory").map(PathBuf::from); | |
| let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from); | |
| let test_builder = matches.opt_str("test-builder").map(PathBuf::from); | |
| let codegen_options_strs = matches.opt_strs("C"); | |
| let unstable_opts_strs = matches.opt_strs("Z"); | |
| let lib_strs = matches.opt_strs("L"); | |
| let extern_strs = matches.opt_strs("extern"); | |
| let test_runtool = matches.opt_str("test-runtool"); | |
| let test_runtool_args = matches.opt_strs("test-runtool-arg"); | |
| let document_private = matches.opt_present("document-private-items"); | |
| let document_hidden = matches.opt_present("document-hidden-items"); | |
| let run_check = matches.opt_present("check"); | |
| let generate_redirect_map = matches.opt_present("generate-redirect-map"); | |
| let show_type_layout = matches.opt_present("show-type-layout"); | |
| let no_capture = matches.opt_present("no-capture"); | |
| let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); | |
| let generate_macro_expansion = matches.opt_present("generate-macro-expansion"); | |
| let extern_html_root_takes_precedence = | |
| matches.opt_present("extern-html-root-takes-precedence"); | |
| let html_no_source = matches.opt_present("html-no-source"); | |
| let merge_doctests = parse_merge_doctests(matches, edition, dcx); | |
| tracing::debug!("merge_doctests: {merge_doctests:?}"); | |
| if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { | |
| dcx.struct_warn( | |
| "`--generate-link-to-definition` option can only be used with HTML output format", | |
| ) | |
| .with_note("`--generate-link-to-definition` option will be ignored") | |
| .emit(); | |
| } | |
| if generate_macro_expansion && (show_coverage || output_format != OutputFormat::Html) { | |
| dcx.struct_warn( | |
| "`--generate-macro-expansion` option can only be used with HTML output format", | |
| ) | |
| .with_note("`--generate-macro-expansion` option will be ignored") | |
| .emit(); | |
| } | |
| let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx); | |
| let with_examples = matches.opt_strs("with-examples"); | |
| let call_locations = | |
| crate::scrape_examples::load_call_locations(with_examples, dcx, &mut loaded_paths); | |
| let doctest_build_args = matches.opt_strs("doctest-build-arg"); | |
| let disable_minification = matches.opt_present("disable-minification"); | |
| let options = Options { | |
| bin_crate, | |
| proc_macro_crate, | |
| error_format, | |
| diagnostic_width, | |
| libs, | |
| lib_strs, | |
| externs, | |
| extern_strs, | |
| cfgs, | |
| check_cfgs, | |
| codegen_options, | |
| codegen_options_strs, | |
| unstable_opts, | |
| unstable_opts_strs, | |
| target, | |
| edition, | |
| sysroot, | |
| lint_opts, | |
| describe_lints, | |
| lint_cap, | |
| prints, | |
| should_test, | |
| test_args, | |
| show_coverage, | |
| crate_version, | |
| test_run_directory, | |
| persist_doctests, | |
| merge_doctests, | |
| test_runtool, | |
| test_runtool_args, | |
| test_builder, | |
| run_check, | |
| no_run, | |
| test_builder_wrappers, | |
| remap_path_prefix, | |
| remap_path_scope, | |
| no_capture, | |
| crate_name, | |
| output_format, | |
| json_unused_externs, | |
| scrape_examples_options, | |
| unstable_features, | |
| doctest_build_args, | |
| target_modifiers: collected_options.target_modifiers, | |
| }; | |
| let render_options = RenderOptions { | |
| output, | |
| external_html, | |
| id_map, | |
| playground_url, | |
| module_sorting, | |
| themes, | |
| extension_css, | |
| extern_html_root_urls, | |
| extern_html_root_takes_precedence, | |
| default_settings, | |
| resource_suffix, | |
| enable_index_page, | |
| index_page, | |
| static_root_path, | |
| markdown_no_toc, | |
| markdown_css, | |
| markdown_playground_url, | |
| document_private, | |
| document_hidden, | |
| generate_redirect_map, | |
| show_type_layout, | |
| unstable_features, | |
| emit, | |
| generate_link_to_definition, | |
| generate_macro_expansion, | |
| call_locations, | |
| no_emit_shared: false, | |
| html_no_source, | |
| output_to_stdout, | |
| should_merge, | |
| include_parts_dir, | |
| parts_out_dir, | |
| disable_minification, | |
| }; | |
| Some((input, options, render_options, loaded_paths)) | |
| } | |
| } | |
| /// Returns `true` if the file given as `self.input` is a Markdown file. | |
| pub(crate) fn markdown_input(input: &Input) -> Option<&Path> { | |
| input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) | |
| } | |
| fn parse_remap_path_prefix( | |
| matches: &getopts::Matches, | |
| ) -> Result<Vec<(PathBuf, PathBuf)>, &'static str> { | |
| matches | |
| .opt_strs("remap-path-prefix") | |
| .into_iter() | |
| .map(|remap| { | |
| remap | |
| .rsplit_once('=') | |
| .ok_or("--remap-path-prefix must contain '=' between FROM and TO") | |
| .map(|(from, to)| (PathBuf::from(from), PathBuf::from(to))) | |
| }) | |
| .collect() | |
| } | |
| /// Prints deprecation warnings for deprecated options | |
| fn check_deprecated_options(matches: &getopts::Matches, dcx: DiagCtxtHandle<'_>) { | |
| let deprecated_flags = []; | |
| for &flag in deprecated_flags.iter() { | |
| if matches.opt_present(flag) { | |
| dcx.struct_warn(format!("the `{flag}` flag is deprecated")) | |
| .with_note( | |
| "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ | |
| for more information", | |
| ) | |
| .emit(); | |
| } | |
| } | |
| let removed_flags = ["plugins", "plugin-path", "no-defaults", "passes", "input-format"]; | |
| for &flag in removed_flags.iter() { | |
| if matches.opt_present(flag) { | |
| let mut err = dcx.struct_warn(format!("the `{flag}` flag no longer functions")); | |
| err.note( | |
| "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ | |
| for more information", | |
| ); | |
| if flag == "no-defaults" || flag == "passes" { | |
| err.help("you may want to use --document-private-items"); | |
| } else if flag == "plugins" || flag == "plugin-path" { | |
| err.warn("see CVE-2018-1000622"); | |
| } | |
| err.emit(); | |
| } | |
| } | |
| } | |
| /// Extracts `--extern-html-root-url` arguments from `matches` and returns a map of crate names to | |
| /// the given URLs. If an `--extern-html-root-url` argument was ill-formed, returns an error | |
| /// describing the issue. | |
| fn parse_extern_html_roots( | |
| matches: &getopts::Matches, | |
| ) -> Result<BTreeMap<String, String>, &'static str> { | |
| let mut externs = BTreeMap::new(); | |
| for arg in &matches.opt_strs("extern-html-root-url") { | |
| let (name, url) = | |
| arg.split_once('=').ok_or("--extern-html-root-url must be of the form name=url")?; | |
| externs.insert(name.to_string(), url.to_string()); | |
| } | |
| Ok(externs) | |
| } | |
| /// Path directly to crate-info directory. | |
| /// | |
| /// For example, `/home/user/project/target/doc.parts`. | |
| /// Each crate has its info stored in a file called `CRATENAME.json`. | |
| #[derive(Clone, Debug)] | |
| pub(crate) struct PathToParts(pub(crate) PathBuf); | |
| impl PathToParts { | |
| fn from_flag(path: String) -> Result<PathToParts, String> { | |
| let path = PathBuf::from(path); | |
| // check here is for diagnostics | |
| if path.exists() && !path.is_dir() { | |
| Err(format!( | |
| "--write-doc-meta-dir and --read-doc-meta-dir expect directories, found: {}", | |
| path.display(), | |
| )) | |
| } else { | |
| // if it doesn't exist, we'll create it. worry about that in write_shared | |
| Ok(PathToParts(path)) | |
| } | |
| } | |
| } | |
| /// Reports error if --read-doc-meta-dir is not a directory | |
| fn parse_read_doc_meta(m: &getopts::Matches, name: &str) -> Result<Vec<PathToParts>, String> { | |
| let mut ret = Vec::new(); | |
| for p in m.opt_strs(name) { | |
| let p = PathToParts::from_flag(p)?; | |
| // this is just for diagnostic | |
| if !p.0.is_dir() { | |
| return Err(format!( | |
| "--read-doc-meta-dir expected {} to be a directory", | |
| p.0.display() | |
| )); | |
| } | |
| ret.push(p); | |
| } | |
| Ok(ret) | |
| } | |
| /// Controls merging of cross-crate information | |
| #[derive(Debug, Clone)] | |
| pub(crate) struct ShouldMerge { | |
| /// Should we append to existing cci in the doc root | |
| pub(crate) read_rendered_cci: bool, | |
| /// Should we write cci to the doc root | |
| pub(crate) write_rendered_cci: bool, | |
| } | |
| /// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or | |
| /// reports an error if an invalid option was provided | |
| fn compute_should_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> { | |
| match (m.opt_present("read-doc-meta-dir"), m.opt_present("write-doc-meta-dir")) { | |
| // shared mode | |
| (false, false) => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }), | |
| // intermediate mode | |
| (false, true) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }), | |
| // finalize mode | |
| (true, false) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }), | |
| // not valid | |
| (true, true) => Err("cannot pass both --read-doc-meta-dir and --write-doc-meta-dir"), | |
| } | |
| } | |
| fn parse_merge_doctests( | |
| m: &getopts::Matches, | |
| edition: Edition, | |
| dcx: DiagCtxtHandle<'_>, | |
| ) -> MergeDoctests { | |
| match m.opt_str("merge-doctests").as_deref() { | |
| Some("y") | Some("yes") | Some("on") | Some("true") => MergeDoctests::Always, | |
| Some("n") | Some("no") | Some("off") | Some("false") => MergeDoctests::Never, | |
| Some("auto") => MergeDoctests::Auto, | |
| None if edition < Edition::Edition2024 => MergeDoctests::Never, | |
| None => MergeDoctests::Auto, | |
| Some(_) => { | |
| dcx.fatal("argument to --merge-doctests must be a boolean (true/false) or 'auto'") | |
| } | |
| } | |
| } |