1use std::collections::BTreeMap;
2use std::ffi::OsStr;
3use std::io::Read;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::{fmt, io};
7
8use rustc_data_structures::fx::FxIndexMap;
9use rustc_errors::DiagCtxtHandle;
10use rustc_lint::Level;
11use rustc_session::config::{
12 self, CodegenOptions, ErrorOutputType, Externs, Input, JsonUnusedExterns,
13 OptionsTargetModifiers, OutFileName, PrintCategory, PrintRequest, Sysroot, UnstableOptions,
14 collect_print_requests, get_cmd_lint_options, nightly_options, parse_crate_types_from_list,
15 parse_externs, parse_target_triple,
16};
17use rustc_session::search_paths::SearchPath;
18use rustc_session::{EarlyDiagCtxt, getopts};
19use rustc_span::edition::Edition;
20use rustc_span::{FileName, RemapPathScopeComponents};
21use rustc_structures::CrateType;
22use rustc_target::spec::TargetTuple;
23use smallvec::SmallVec;
24
25use crate::core::new_dcx;
26use crate::externalfiles::ExternalHtml;
27use crate::html::markdown::IdMap;
28use crate::html::render::StylePath;
29use crate::html::static_files;
30use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions};
31use crate::{html, opts, theme};
32
33#[derive(Clone, Copy, PartialEq, Eq, Debug)]
34pub(crate) enum OutputFormat {
35 IrJson,
39 CoverageJson,
41 Html,
42 Doctest,
43}
44
45pub(crate) enum InputMode {
47 NoInputMergeFinalize,
49 HasFile(Input),
51}
52
53#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
55pub(crate) enum MergeDoctests {
56 #[default]
57 Never,
58 Always,
59 Auto,
60}
61
62#[derive(Clone)]
64pub(crate) struct Options {
65 pub(crate) crate_name: Option<String>,
68 pub(crate) bin_crate: bool,
70 pub(crate) proc_macro_crate: bool,
72 pub(crate) error_format: ErrorOutputType,
74 pub(crate) diagnostic_width: Option<usize>,
76 pub(crate) libs: Vec<SearchPath>,
78 pub(crate) lib_strs: Vec<String>,
80 pub(crate) externs: Externs,
82 pub(crate) extern_strs: Vec<String>,
84 pub(crate) cfgs: Vec<String>,
86 pub(crate) check_cfgs: Vec<String>,
88 pub(crate) codegen_options: CodegenOptions,
90 pub(crate) codegen_options_strs: Vec<String>,
92 pub(crate) unstable_opts: UnstableOptions,
94 pub(crate) unstable_opts_strs: Vec<String>,
96 pub(crate) target: TargetTuple,
98 pub(crate) edition: Edition,
101 pub(crate) sysroot: Sysroot,
103 pub(crate) lint_opts: Vec<(String, Level)>,
105 pub(crate) describe_lints: bool,
107 pub(crate) lint_cap: Option<Level>,
109 pub(crate) prints: Vec<PrintRequest>,
111
112 pub(crate) should_test: bool,
115 pub(crate) test_args: Vec<String>,
117 pub(crate) test_run_directory: Option<PathBuf>,
119 pub(crate) persist_doctests: Option<PathBuf>,
122 pub(crate) merge_doctests: MergeDoctests,
124 pub(crate) test_runtool: Option<String>,
126 pub(crate) test_runtool_args: Vec<String>,
128 pub(crate) no_run: bool,
130 pub(crate) remap_path_prefix: Vec<(PathBuf, PathBuf)>,
132 pub(crate) remap_path_prefix_strs: Vec<String>,
133 pub(crate) remap_path_scope: RemapPathScopeComponents,
135 pub(crate) remap_path_scope_str: Option<String>,
136
137 pub(crate) test_builder: Option<PathBuf>,
140
141 pub(crate) test_builder_wrappers: Vec<PathBuf>,
143
144 pub(crate) show_coverage: bool,
148
149 pub(crate) crate_version: Option<String>,
152 pub(crate) output_format: OutputFormat,
156 pub(crate) run_check: bool,
159 pub(crate) json_unused_externs: JsonUnusedExterns,
161 pub(crate) no_capture: bool,
163
164 pub(crate) scrape_examples_options: Option<ScrapeExamplesOptions>,
167
168 pub(crate) unstable_features: rustc_feature::UnstableFeatures,
171
172 pub(crate) doctest_build_args: Vec<String>,
174
175 pub(crate) target_modifiers: BTreeMap<OptionsTargetModifiers, String>,
177}
178
179impl fmt::Debug for Options {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 struct FmtExterns<'a>(&'a Externs);
182
183 impl fmt::Debug for FmtExterns<'_> {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.debug_map().entries(self.0.iter()).finish()
186 }
187 }
188
189 f.debug_struct("Options")
190 .field("crate_name", &self.crate_name)
191 .field("bin_crate", &self.bin_crate)
192 .field("proc_macro_crate", &self.proc_macro_crate)
193 .field("error_format", &self.error_format)
194 .field("libs", &self.libs)
195 .field("externs", &FmtExterns(&self.externs))
196 .field("cfgs", &self.cfgs)
197 .field("check-cfgs", &self.check_cfgs)
198 .field("codegen_options", &"...")
199 .field("unstable_options", &"...")
200 .field("target", &self.target)
201 .field("edition", &self.edition)
202 .field("sysroot", &self.sysroot)
203 .field("lint_opts", &self.lint_opts)
204 .field("describe_lints", &self.describe_lints)
205 .field("lint_cap", &self.lint_cap)
206 .field("prints", &self.prints)
207 .field("should_test", &self.should_test)
208 .field("test_args", &self.test_args)
209 .field("test_run_directory", &self.test_run_directory)
210 .field("persist_doctests", &self.persist_doctests)
211 .field("show_coverage", &self.show_coverage)
212 .field("crate_version", &self.crate_version)
213 .field("test_runtool", &self.test_runtool)
214 .field("test_runtool_args", &self.test_runtool_args)
215 .field("run_check", &self.run_check)
216 .field("no_run", &self.no_run)
217 .field("test_builder_wrappers", &self.test_builder_wrappers)
218 .field("remap-file-prefix", &self.remap_path_prefix)
219 .field("remap-file-scope", &self.remap_path_scope)
220 .field("no_capture", &self.no_capture)
221 .field("scrape_examples_options", &self.scrape_examples_options)
222 .field("unstable_features", &self.unstable_features)
223 .finish()
224 }
225}
226
227#[derive(Clone, Debug)]
229pub(crate) struct RenderOptions {
230 pub(crate) output: PathBuf,
232 pub(crate) external_html: ExternalHtml,
234 pub(crate) id_map: IdMap,
237 pub(crate) playground_url: Option<String>,
241 pub(crate) module_sorting: ModuleSorting,
244 pub(crate) crate_list_heading: String,
248 pub(crate) themes: Vec<StylePath>,
251 pub(crate) extension_css: Option<PathBuf>,
253 pub(crate) extern_html_root_urls: BTreeMap<String, String>,
255 pub(crate) extern_html_root_takes_precedence: bool,
257 pub(crate) default_settings: FxIndexMap<String, String>,
260 pub(crate) resource_suffix: String,
262 pub(crate) enable_index_page: bool,
265 pub(crate) index_page: Option<PathBuf>,
268 pub(crate) static_root_path: Option<String>,
271
272 pub(crate) markdown_no_toc: bool,
276 pub(crate) markdown_css: Vec<String>,
278 pub(crate) markdown_playground_url: Option<String>,
281 pub(crate) document_private: bool,
283 pub(crate) document_hidden: bool,
285 pub(crate) generate_redirect_map: bool,
287 pub(crate) show_type_layout: bool,
289 pub(crate) unstable_features: rustc_feature::UnstableFeatures,
292 pub(crate) emit: SmallVec<[EmitType; 2]>,
293 pub(crate) generate_link_to_definition: bool,
295 pub(crate) call_locations: AllCallLocations,
297 pub(crate) no_emit_shared: bool,
299 pub(crate) html_no_source: bool,
301 pub(crate) output_to_stdout: bool,
304 pub(crate) should_merge: ShouldMerge,
306 pub(crate) include_parts_dir: Vec<PathToParts>,
308 pub(crate) parts_out_dir: Option<PathToParts>,
310 pub(crate) disable_minification: bool,
312 pub(crate) generate_macro_expansion: bool,
314}
315
316#[derive(Copy, Clone, Debug, PartialEq, Eq)]
317pub(crate) enum ModuleSorting {
318 DeclarationOrder,
319 Alphabetical,
320}
321
322#[derive(Clone, Debug, PartialEq, Eq)]
323pub(crate) enum EmitType {
324 HtmlStaticFiles,
325 HtmlNonStaticFiles,
326 IrJsonFiles,
328 CoverageJsonFiles,
329 DepInfo(Option<OutFileName>),
330}
331
332impl fmt::Display for EmitType {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 f.write_str(match self {
335 Self::HtmlStaticFiles => "html-static-files",
336 Self::HtmlNonStaticFiles => "html-non-static-files",
337 Self::IrJsonFiles => "ir-json-files",
338 Self::CoverageJsonFiles => "coverage-json-files",
339 Self::DepInfo(_) => "dep-info",
340 })
341 }
342}
343
344impl FromStr for EmitType {
345 type Err = ();
346
347 fn from_str(s: &str) -> Result<Self, Self::Err> {
348 match s {
349 "html-static-files" => Ok(Self::HtmlStaticFiles),
350 "html-non-static-files" => Ok(Self::HtmlNonStaticFiles),
351 "dep-info" => Ok(Self::DepInfo(None)),
352 option => match option.strip_prefix("dep-info=") {
353 Some("-") => Ok(Self::DepInfo(Some(OutFileName::Stdout))),
354 Some(f) => Ok(Self::DepInfo(Some(OutFileName::Real(f.into())))),
355 None => Err(()),
356 },
357 }
358 }
359}
360
361impl RenderOptions {
362 pub(crate) fn dep_info(&self) -> Option<Option<&OutFileName>> {
363 self.emit.iter().find_map(|emit| match emit {
364 EmitType::DepInfo(file) => Some(file.as_ref()),
365 _ => None,
366 })
367 }
368}
369
370fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input {
374 if input == "-" {
375 let mut src = String::new();
376 if io::stdin().read_to_string(&mut src).is_err() {
377 early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8");
380 }
381 Input::Str { name: FileName::anon_source_code(&src), input: src }
382 } else {
383 Input::File(PathBuf::from(input))
384 }
385}
386
387impl Options {
388 pub(crate) fn from_matches(
391 early_dcx: &mut EarlyDiagCtxt,
392 matches: &getopts::Matches,
393 args: Vec<String>,
394 ) -> Option<(InputMode, Options, RenderOptions, Vec<PathBuf>)> {
395 nightly_options::check_nightly_options(early_dcx, matches, &opts());
397
398 if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") {
399 crate::usage("rustdoc");
400 return None;
401 } else if matches.opt_present("version") {
402 rustc_driver::version!(&early_dcx, "rustdoc", matches);
403 return None;
404 }
405
406 if rustc_driver::describe_flag_categories(early_dcx, matches) {
407 return None;
408 }
409
410 let color = config::parse_color(early_dcx, matches);
411 let crate_name = matches.opt_str("crate-name");
412 let unstable_features =
413 rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
414 let config::JsonConfig { json_rendered, json_unused_externs, json_color, .. } =
415 config::parse_json(early_dcx, matches);
416 let error_format =
417 config::parse_error_format(early_dcx, matches, color, json_color, json_rendered);
418 let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default();
419
420 let mut collected_options = Default::default();
421 let mut codegen_options = CodegenOptions::build(early_dcx, matches, &mut collected_options);
422 let unstable_opts = UnstableOptions::build(early_dcx, matches, &mut collected_options);
423
424 let remap_path_prefix = match parse_remap_path_prefix(matches) {
425 Ok(prefix_mappings) => prefix_mappings,
426 Err(err) => {
427 early_dcx.early_fatal(err);
428 }
429 };
430 let remap_path_scope =
431 rustc_session::config::parse_remap_path_scope(early_dcx, matches, &unstable_opts);
432
433 let dcx = new_dcx(error_format, None, diagnostic_width, &unstable_opts);
434 let dcx = dcx.handle();
435
436 check_deprecated_options(matches, dcx);
438
439 let should_test = matches.opt_present("test");
440 let show_coverage = matches.opt_present("show-coverage");
441 let output_format_s = matches.opt_str("output-format");
442 let output_format = match output_format_s.as_deref() {
443 None | Some("html") => OutputFormat::Html,
444 Some("json") => {
445 if show_coverage {
446 OutputFormat::CoverageJson
447 } else {
448 OutputFormat::IrJson
449 }
450 }
451 Some("doctest") => OutputFormat::Doctest,
452 Some(other) => dcx.fatal(format!("unknown output format `{other}`")),
453 };
454
455 match (
457 output_format_s.as_ref().map(|_| output_format),
458 show_coverage,
459 nightly_options::unstable_options_enabled(matches),
460 ) {
461 (None | Some(OutputFormat::CoverageJson), true, _) => {}
462 (_, true, _) => {
463 dcx.fatal(format!(
464 "`--output-format={}` is not supported for the `--show-coverage` option",
465 output_format_s.expect("checked for none above"),
466 ));
467 }
468 (_, false, true) => {}
470 (None | Some(OutputFormat::Html), false, _) => {}
471 (Some(OutputFormat::IrJson), false, false) => {
472 dcx.fatal(
473 "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)",
474 );
475 }
476 (Some(OutputFormat::Doctest), false, false) => {
477 dcx.fatal(
478 "the -Z unstable-options flag must be passed to enable --output-format=doctest (see https://github.com/rust-lang/rust/issues/134529)",
479 );
480 }
481 (Some(OutputFormat::CoverageJson), false, _) => {
482 unreachable!("CoverageJson is only possible when show_coverage is true")
483 }
484 }
485
486 let mut emit = FxIndexMap::default();
487 for list in matches.opt_strs("emit") {
488 if should_test {
489 dcx.fatal("the `--test` flag and the `--emit` flag are not supported together");
490 }
491 if let OutputFormat::Doctest = output_format {
492 dcx.fatal("the `--emit` flag is not supported with `--output-format=doctest`");
493 }
494
495 for typ in list.split(',') {
496 let Ok(typ) = typ.parse::<EmitType>() else {
497 dcx.fatal(format!("unrecognized emission type: {typ}"))
498 };
499
500 match typ {
501 EmitType::DepInfo(_) => match output_format {
502 OutputFormat::Html | OutputFormat::IrJson | OutputFormat::CoverageJson => {}
503 OutputFormat::Doctest => unreachable!(),
504 },
505 EmitType::HtmlStaticFiles | EmitType::HtmlNonStaticFiles => match output_format
506 {
507 OutputFormat::Html => {}
508 OutputFormat::IrJson | OutputFormat::CoverageJson => dcx.fatal(format!(
509 "the `--emit={typ}` flag is not supported with `--output-format=json`",
510 )),
511 OutputFormat::Doctest => unreachable!(),
512 },
513 EmitType::IrJsonFiles | EmitType::CoverageJsonFiles => unreachable!(),
514 }
515
516 emit.insert(std::mem::discriminant(&typ), typ);
521 }
522 }
523 let mut emit: SmallVec<[_; 2]> = emit.into_values().collect();
524 if emit.is_empty() {
528 match output_format {
529 OutputFormat::IrJson => emit.push(EmitType::IrJsonFiles),
530 OutputFormat::CoverageJson => emit.push(EmitType::CoverageJsonFiles),
531 OutputFormat::Html => {
532 emit.push(EmitType::HtmlStaticFiles);
533 emit.push(EmitType::HtmlNonStaticFiles);
534 }
535 OutputFormat::Doctest => {}
536 }
537 }
538
539 let to_check = matches.opt_strs("check-theme");
540 if !to_check.is_empty() {
541 let mut content =
542 std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap();
543 if let Some((_, inside)) = content.split_once("/* Begin theme: light */") {
544 content = inside;
545 }
546 if let Some((inside, _)) = content.split_once("/* End theme: light */") {
547 content = inside;
548 }
549 let paths = match theme::load_css_paths(content) {
550 Ok(p) => p,
551 Err(e) => dcx.fatal(e),
552 };
553 let mut errors = 0;
554
555 println!("rustdoc: [check-theme] Starting tests! (Ignoring all other arguments)");
556 for theme_file in to_check.iter() {
557 print!(" - Checking \"{theme_file}\"...");
558 let (success, differences) = theme::test_theme_against(theme_file, &paths, dcx);
559 if !differences.is_empty() || !success {
560 println!(" FAILED");
561 errors += 1;
562 if !differences.is_empty() {
563 println!("{}", differences.join("\n"));
564 }
565 } else {
566 println!(" OK");
567 }
568 }
569 if errors != 0 {
570 dcx.fatal("[check-theme] one or more tests failed");
571 }
572 return None;
573 }
574
575 let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
576
577 let externs = parse_externs(early_dcx, matches, &unstable_opts);
578 let extern_html_root_urls = match parse_extern_html_roots(matches) {
579 Ok(ex) => ex,
580 Err(err) => dcx.fatal(err),
581 };
582
583 let prints = collect_print_requests(
584 early_dcx,
585 &mut codegen_options,
586 &unstable_opts,
587 matches,
588 &[PrintCategory::Target, PrintCategory::Crate],
589 );
590
591 let mut parts_out_dir =
592 match matches.opt_str("write-doc-meta-dir").map(PathToParts::from_flag).transpose() {
593 Ok(parts_out_dir) => parts_out_dir,
594 Err(e) => dcx.fatal(e),
595 };
596 let mut include_parts_dir = match parse_read_doc_meta(matches, "read-doc-meta-dir") {
597 Ok(include_parts_dir) => include_parts_dir,
598 Err(e) => dcx.fatal(e),
599 };
600 let mut should_merge = match compute_should_merge(matches) {
601 Ok(should_merge) => should_merge,
602 Err(e) => dcx.fatal(e),
603 };
604 if parts_out_dir.is_none() && include_parts_dir.is_empty() {
605 parts_out_dir =
607 match matches.opt_str("parts-out-dir").map(PathToParts::from_flag).transpose() {
608 Ok(parts_out_dir) => parts_out_dir,
609 Err(e) => dcx.fatal(e),
610 };
611 include_parts_dir = match parse_read_doc_meta(matches, "include-parts-dir") {
612 Ok(include_parts_dir) => include_parts_dir,
613 Err(e) => dcx.fatal(e),
614 };
615 should_merge = match matches.opt_str("merge").as_deref() {
616 None => ShouldMerge { read_rendered_cci: true, write_rendered_cci: true },
617 Some("none") => ShouldMerge { read_rendered_cci: false, write_rendered_cci: false },
618 Some("shared") => ShouldMerge { read_rendered_cci: true, write_rendered_cci: true },
619 Some("finalize") => {
620 ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }
621 }
622 Some(_) => dcx.fatal("argument to --merge must be `none`, `shared`, or `finalize`"),
623 };
624 } else if matches.opt_str("parts-out-dir").is_some() {
625 dcx.fatal(
626 "deprecated version of write-doc-meta-dir is used with new doc-meta-dir stuff",
627 );
628 } else if matches.opt_str("include-parts-dir").is_some() {
629 dcx.fatal(
630 "deprecated version of read-doc-meta-dir is used with new doc-meta-dir stuff",
631 );
632 } else if matches.opt_str("merge").is_some() {
633 dcx.fatal("deprecated parameter merge is used with new doc-meta-dir stuff");
634 }
635
636 let input = if describe_lints {
637 InputMode::HasFile(make_input(early_dcx, ""))
638 } else {
639 match matches.free.as_slice() {
640 [] if !include_parts_dir.is_empty() && should_merge.write_rendered_cci => {
641 InputMode::NoInputMergeFinalize
642 }
643 [] => dcx.fatal("missing file operand"),
644 [input] => InputMode::HasFile(make_input(early_dcx, input)),
645 _ => dcx.fatal("too many file operands"),
646 }
647 };
648
649 let default_settings: Vec<Vec<(String, String)>> = vec![
650 matches
651 .opt_str("default-theme")
652 .iter()
653 .flat_map(|theme| {
654 vec![
655 ("use-system-theme".to_string(), "false".to_string()),
656 ("theme".to_string(), theme.to_string()),
657 ]
658 })
659 .collect(),
660 matches
661 .opt_strs("default-setting")
662 .iter()
663 .map(|s| match s.split_once('=') {
664 None => (s.clone(), "true".to_string()),
665 Some((k, v)) => (k.to_string(), v.to_string()),
666 })
667 .collect(),
668 ];
669 let default_settings = default_settings
670 .into_iter()
671 .flatten()
672 .map(
673 |(k, v)| (k.replace('-', "_"), v),
692 )
693 .collect();
694
695 let test_args = matches.opt_strs("test-args");
696 let test_args: Vec<String> =
697 test_args.iter().flat_map(|s| s.split_whitespace()).map(|s| s.to_string()).collect();
698
699 let no_run = matches.opt_present("no-run");
700
701 if !should_test && no_run {
702 dcx.fatal("the `--test` flag must be passed to enable `--no-run`");
703 }
704
705 let mut output_to_stdout = false;
706 let test_builder_wrappers =
707 matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect();
708 let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) {
709 (Some(_), Some(_)) => {
710 dcx.fatal("cannot use both 'out-dir' and 'output' at once");
711 }
712 (Some(out_dir), None) | (None, Some(out_dir)) => {
713 output_to_stdout = out_dir == "-";
714 PathBuf::from(out_dir)
715 }
716 (None, None) => {
717 if show_coverage {
718 output_to_stdout = true;
721 }
722 PathBuf::from("doc")
723 }
724 };
725
726 let cfgs = matches.opt_strs("cfg");
727 let check_cfgs = matches.opt_strs("check-cfg");
728
729 let extension_css = matches.opt_str("e").map(|s| PathBuf::from(&s));
730
731 let mut loaded_paths = Vec::new();
732
733 if let Some(ref p) = extension_css {
734 loaded_paths.push(p.clone());
735 if !p.is_file() {
736 dcx.fatal("option --extend-css argument must be a file");
737 }
738 }
739
740 let mut themes = Vec::new();
741 if matches.opt_present("theme") {
742 let mut content =
743 std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap();
744 if let Some((_, inside)) = content.split_once("/* Begin theme: light */") {
745 content = inside;
746 }
747 if let Some((inside, _)) = content.split_once("/* End theme: light */") {
748 content = inside;
749 }
750 let paths = match theme::load_css_paths(content) {
751 Ok(p) => p,
752 Err(e) => dcx.fatal(e),
753 };
754
755 for (theme_file, theme_s) in
756 matches.opt_strs("theme").iter().map(|s| (PathBuf::from(&s), s.to_owned()))
757 {
758 if !theme_file.is_file() {
759 dcx.struct_fatal(format!("invalid argument: \"{theme_s}\""))
760 .with_help("arguments to --theme must be files")
761 .emit_fatal();
762 }
763 if theme_file.extension() != Some(OsStr::new("css")) {
764 dcx.struct_fatal(format!("invalid argument: \"{theme_s}\""))
765 .with_help("arguments to --theme must have a .css extension")
766 .emit_fatal();
767 }
768 let (success, ret) = theme::test_theme_against(&theme_file, &paths, dcx);
769 if !success {
770 dcx.fatal(format!("error loading theme file: \"{theme_s}\""));
771 } else if !ret.is_empty() {
772 dcx.struct_warn(format!(
773 "theme file \"{theme_s}\" is missing CSS rules from the default theme",
774 ))
775 .with_warn("the theme may appear incorrect when loaded")
776 .with_help(format!(
777 "to see what rules are missing, call `rustdoc --check-theme \"{theme_s}\"`",
778 ))
779 .emit();
780 }
781 loaded_paths.push(theme_file.clone());
782 themes.push(StylePath { path: theme_file });
783 }
784 }
785
786 let edition = config::parse_crate_edition(early_dcx, matches, true);
787
788 let mut id_map = html::markdown::IdMap::new();
789 let Some(external_html) = ExternalHtml::load(
790 &matches.opt_strs("html-in-header"),
791 &matches.opt_strs("html-before-content"),
792 &matches.opt_strs("html-after-content"),
793 &matches.opt_strs("markdown-before-content"),
794 &matches.opt_strs("markdown-after-content"),
795 nightly_options::match_is_nightly_build(matches),
796 dcx,
797 &mut id_map,
798 edition,
799 &None,
800 &mut loaded_paths,
801 ) else {
802 dcx.fatal("`ExternalHtml::load` failed");
803 };
804
805 match matches.opt_str("r").as_deref() {
806 Some("rust") | None => {}
807 Some(s) => dcx.fatal(format!("unknown input format: {s}")),
808 }
809
810 let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s));
811 if let Some(ref index_page) = index_page {
812 if index_page.is_file() {
813 loaded_paths.push(index_page.clone());
814 } else {
815 dcx.fatal("option `--index-page` argument must be a file");
816 }
817 }
818
819 let target = parse_target_triple(early_dcx, matches);
820 let sysroot = Sysroot::new(matches.opt_str("sysroot").map(PathBuf::from));
821
822 let libs = matches
823 .opt_strs("L")
824 .iter()
825 .map(|s| {
826 SearchPath::from_cli_opt(
827 sysroot.path(),
828 &target,
829 early_dcx,
830 s,
831 #[allow(rustc::bad_opt_access)] unstable_opts.unstable_options,
833 )
834 })
835 .collect();
836
837 let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) {
838 Ok(types) => types,
839 Err(e) => {
840 dcx.fatal(format!("unknown crate type: {e}"));
841 }
842 };
843
844 let bin_crate = crate_types.contains(&CrateType::Executable);
845 let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
846 let playground_url = matches.opt_str("playground-url");
847 let module_sorting = if matches.opt_present("sort-modules-by-appearance") {
848 ModuleSorting::DeclarationOrder
849 } else {
850 ModuleSorting::Alphabetical
851 };
852 let crate_list_heading = matches.opt_str("crate-list-heading").unwrap_or_default();
853 let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default();
854 let markdown_no_toc = matches.opt_present("markdown-no-toc");
855 let markdown_css = matches.opt_strs("markdown-css");
856 let markdown_playground_url = matches.opt_str("markdown-playground-url");
857 let crate_version = matches.opt_str("crate-version");
858 let enable_index_page = matches.opt_present("enable-index-page") || index_page.is_some();
859 let static_root_path = matches.opt_str("static-root-path");
860 let test_run_directory = matches.opt_str("test-run-directory").map(PathBuf::from);
861 let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from);
862 let test_builder = matches.opt_str("test-builder").map(PathBuf::from);
863 let codegen_options_strs = matches.opt_strs("C");
864 let unstable_opts_strs = matches.opt_strs("Z");
865 let lib_strs = matches.opt_strs("L");
866 let extern_strs = matches.opt_strs("extern");
867 let remap_path_prefix_strs = matches.opt_strs("remap-path-prefix");
868 let remap_path_scope_str = matches.opt_str("remap-path-scope");
869 let test_runtool = matches.opt_str("test-runtool");
870 let test_runtool_args = matches.opt_strs("test-runtool-arg");
871 let document_private = matches.opt_present("document-private-items");
872 let document_hidden = matches.opt_present("document-hidden-items");
873 let run_check = matches.opt_present("check");
874 let generate_redirect_map = matches.opt_present("generate-redirect-map");
875 let show_type_layout = matches.opt_present("show-type-layout");
876 let no_capture = matches.opt_present("no-capture");
877 let generate_link_to_definition = matches.opt_present("generate-link-to-definition");
878 let generate_macro_expansion = matches.opt_present("generate-macro-expansion");
879 let extern_html_root_takes_precedence =
880 matches.opt_present("extern-html-root-takes-precedence");
881 let html_no_source = matches.opt_present("html-no-source");
882 let merge_doctests = parse_merge_doctests(matches, edition, dcx);
883 tracing::debug!("merge_doctests: {merge_doctests:?}");
884
885 if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
886 dcx.struct_warn(
887 "`--generate-link-to-definition` option can only be used with HTML output format",
888 )
889 .with_note("`--generate-link-to-definition` option will be ignored")
890 .emit();
891 }
892 if generate_macro_expansion && (show_coverage || output_format != OutputFormat::Html) {
893 dcx.struct_warn(
894 "`--generate-macro-expansion` option can only be used with HTML output format",
895 )
896 .with_note("`--generate-macro-expansion` option will be ignored")
897 .emit();
898 }
899
900 let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);
901 let with_examples = matches.opt_strs("with-examples");
902 let call_locations =
903 crate::scrape_examples::load_call_locations(with_examples, dcx, &mut loaded_paths);
904 let doctest_build_args = matches.opt_strs("doctest-build-arg");
905
906 let disable_minification = matches.opt_present("disable-minification");
907
908 let options = Options {
909 bin_crate,
910 proc_macro_crate,
911 error_format,
912 diagnostic_width,
913 libs,
914 lib_strs,
915 externs,
916 extern_strs,
917 cfgs,
918 check_cfgs,
919 codegen_options,
920 codegen_options_strs,
921 unstable_opts,
922 unstable_opts_strs,
923 target,
924 edition,
925 sysroot,
926 lint_opts,
927 describe_lints,
928 lint_cap,
929 prints,
930 should_test,
931 test_args,
932 show_coverage,
933 crate_version,
934 test_run_directory,
935 persist_doctests,
936 merge_doctests,
937 test_runtool,
938 test_runtool_args,
939 test_builder,
940 run_check,
941 no_run,
942 test_builder_wrappers,
943 remap_path_prefix,
944 remap_path_prefix_strs,
945 remap_path_scope,
946 remap_path_scope_str,
947 no_capture,
948 crate_name,
949 output_format,
950 json_unused_externs,
951 scrape_examples_options,
952 unstable_features: unstable_features.clone(),
953 doctest_build_args,
954 target_modifiers: collected_options.target_modifiers,
955 };
956 let render_options = RenderOptions {
957 output,
958 external_html,
959 id_map,
960 playground_url,
961 module_sorting,
962 crate_list_heading,
963 themes,
964 extension_css,
965 extern_html_root_urls,
966 extern_html_root_takes_precedence,
967 default_settings,
968 resource_suffix,
969 enable_index_page,
970 index_page,
971 static_root_path,
972 markdown_no_toc,
973 markdown_css,
974 markdown_playground_url,
975 document_private,
976 document_hidden,
977 generate_redirect_map,
978 show_type_layout,
979 unstable_features,
980 emit,
981 generate_link_to_definition,
982 generate_macro_expansion,
983 call_locations,
984 no_emit_shared: false,
985 html_no_source,
986 output_to_stdout,
987 should_merge,
988 include_parts_dir,
989 parts_out_dir,
990 disable_minification,
991 };
992 Some((input, options, render_options, loaded_paths))
993 }
994}
995
996pub(crate) fn markdown_input(input: &Input) -> Option<&Path> {
998 input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
999}
1000
1001fn parse_remap_path_prefix(
1002 matches: &getopts::Matches,
1003) -> Result<Vec<(PathBuf, PathBuf)>, &'static str> {
1004 matches
1005 .opt_strs("remap-path-prefix")
1006 .into_iter()
1007 .map(|remap| {
1008 remap
1009 .rsplit_once('=')
1010 .ok_or("--remap-path-prefix must contain '=' between FROM and TO")
1011 .map(|(from, to)| (PathBuf::from(from), PathBuf::from(to)))
1012 })
1013 .collect()
1014}
1015
1016fn check_deprecated_options(matches: &getopts::Matches, dcx: DiagCtxtHandle<'_>) {
1018 let deprecated_flags = [];
1019
1020 for &flag in deprecated_flags.iter() {
1021 if matches.opt_present(flag) {
1022 dcx.struct_warn(format!("the `{flag}` flag is deprecated"))
1023 .with_note(
1024 "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
1025 for more information",
1026 )
1027 .emit();
1028 }
1029 }
1030
1031 let removed_flags = ["plugins", "plugin-path", "no-defaults", "passes", "input-format"];
1032
1033 for &flag in removed_flags.iter() {
1034 if matches.opt_present(flag) {
1035 let mut err = dcx.struct_warn(format!("the `{flag}` flag no longer functions"));
1036 err.note(
1037 "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
1038 for more information",
1039 );
1040
1041 if flag == "no-defaults" || flag == "passes" {
1042 err.help("you may want to use --document-private-items");
1043 } else if flag == "plugins" || flag == "plugin-path" {
1044 err.warn("see CVE-2018-1000622");
1045 }
1046
1047 err.emit();
1048 }
1049 }
1050}
1051
1052fn parse_extern_html_roots(
1056 matches: &getopts::Matches,
1057) -> Result<BTreeMap<String, String>, &'static str> {
1058 let mut externs = BTreeMap::new();
1059 for arg in &matches.opt_strs("extern-html-root-url") {
1060 let (name, url) =
1061 arg.split_once('=').ok_or("--extern-html-root-url must be of the form name=url")?;
1062 externs.insert(name.to_string(), url.to_string());
1063 }
1064 Ok(externs)
1065}
1066
1067#[derive(Clone, Debug)]
1072pub(crate) struct PathToParts(pub(crate) PathBuf);
1073
1074impl PathToParts {
1075 fn from_flag(path: String) -> Result<PathToParts, String> {
1076 let path = PathBuf::from(path);
1077 if path.exists() && !path.is_dir() {
1079 Err(format!(
1080 "--write-doc-meta-dir and --read-doc-meta-dir expect directories, found: {}",
1081 path.display(),
1082 ))
1083 } else {
1084 Ok(PathToParts(path))
1086 }
1087 }
1088}
1089
1090fn parse_read_doc_meta(m: &getopts::Matches, name: &str) -> Result<Vec<PathToParts>, String> {
1092 let mut ret = Vec::new();
1093 for p in m.opt_strs(name) {
1094 let p = PathToParts::from_flag(p)?;
1095 if !p.0.is_dir() {
1097 return Err(format!(
1098 "--read-doc-meta-dir expected {} to be a directory",
1099 p.0.display()
1100 ));
1101 }
1102 ret.push(p);
1103 }
1104 Ok(ret)
1105}
1106
1107#[derive(Debug, Clone)]
1109pub(crate) struct ShouldMerge {
1110 pub(crate) read_rendered_cci: bool,
1112 pub(crate) write_rendered_cci: bool,
1114}
1115
1116fn compute_should_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> {
1119 match (m.opt_present("read-doc-meta-dir"), m.opt_present("write-doc-meta-dir")) {
1120 (false, false) => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
1122 (false, true) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }),
1124 (true, false) => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }),
1126 (true, true) => Err("cannot pass both --read-doc-meta-dir and --write-doc-meta-dir"),
1128 }
1129}
1130
1131fn parse_merge_doctests(
1132 m: &getopts::Matches,
1133 edition: Edition,
1134 dcx: DiagCtxtHandle<'_>,
1135) -> MergeDoctests {
1136 match m.opt_str("merge-doctests").as_deref() {
1137 Some("y") | Some("yes") | Some("on") | Some("true") => MergeDoctests::Always,
1138 Some("n") | Some("no") | Some("off") | Some("false") => MergeDoctests::Never,
1139 Some("auto") => MergeDoctests::Auto,
1140 None if edition < Edition::Edition2024 => MergeDoctests::Never,
1141 None => MergeDoctests::Auto,
1142 Some(_) => {
1143 dcx.fatal("argument to --merge-doctests must be a boolean (true/false) or 'auto'")
1144 }
1145 }
1146}