rustdoc/
lib.rs

1// tidy-alphabetical-start
2#![cfg_attr(bootstrap, feature(debug_closure_helpers))]
3#![doc(
4    html_root_url = "https://doc.rust-lang.org/nightly/",
5    html_playground_url = "https://play.rust-lang.org/"
6)]
7#![feature(ascii_char)]
8#![feature(ascii_char_variants)]
9#![feature(assert_matches)]
10#![feature(box_into_inner)]
11#![feature(box_patterns)]
12#![feature(file_buffered)]
13#![feature(formatting_options)]
14#![feature(if_let_guard)]
15#![feature(iter_advance_by)]
16#![feature(iter_intersperse)]
17#![feature(iter_order_by)]
18#![feature(rustc_private)]
19#![feature(test)]
20#![feature(trim_prefix_suffix)]
21#![warn(rustc::internal)]
22// tidy-alphabetical-end
23
24// N.B. these need `extern crate` even in 2018 edition
25// because they're loaded implicitly from the sysroot.
26// The reason they're loaded from the sysroot is because
27// the rustdoc artifacts aren't stored in rustc's cargo target directory.
28// So if `rustc` was specified in Cargo.toml, this would spuriously rebuild crates.
29//
30// Dependencies listed in Cargo.toml do not need `extern crate`.
31
32extern crate rustc_abi;
33extern crate rustc_ast;
34extern crate rustc_ast_pretty;
35extern crate rustc_attr_parsing;
36extern crate rustc_data_structures;
37extern crate rustc_driver;
38extern crate rustc_errors;
39extern crate rustc_feature;
40extern crate rustc_hir;
41extern crate rustc_hir_analysis;
42extern crate rustc_hir_pretty;
43extern crate rustc_index;
44extern crate rustc_infer;
45extern crate rustc_interface;
46extern crate rustc_lexer;
47extern crate rustc_lint;
48extern crate rustc_lint_defs;
49extern crate rustc_log;
50extern crate rustc_macros;
51extern crate rustc_metadata;
52extern crate rustc_middle;
53extern crate rustc_parse;
54extern crate rustc_passes;
55extern crate rustc_resolve;
56extern crate rustc_serialize;
57extern crate rustc_session;
58extern crate rustc_span;
59extern crate rustc_target;
60extern crate rustc_trait_selection;
61extern crate test;
62
63/// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs
64/// and https://github.com/rust-lang/rust/pull/146627 for why we need this.
65///
66/// FIXME(madsmtm): This is loaded from the sysroot that was built with the other `rustc` crates
67/// above, instead of via Cargo as you'd normally do. This is currently needed for LTO due to
68/// https://github.com/rust-lang/cc-rs/issues/1613.
69#[cfg(feature = "jemalloc")]
70extern crate tikv_jemalloc_sys as _;
71
72use std::env::{self, VarError};
73use std::io::{self, IsTerminal};
74use std::path::Path;
75use std::process;
76
77use rustc_errors::DiagCtxtHandle;
78use rustc_hir::def_id::LOCAL_CRATE;
79use rustc_hir::lints::DelayedLint;
80use rustc_interface::interface;
81use rustc_middle::ty::TyCtxt;
82use rustc_session::config::{ErrorOutputType, RustcOptGroup, make_crate_type_option};
83use rustc_session::{EarlyDiagCtxt, getopts};
84use tracing::info;
85
86use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION;
87use crate::error::Error;
88use crate::formats::cache::Cache;
89
90/// A macro to create a FxHashMap.
91///
92/// Example:
93///
94/// ```ignore(cannot-test-this-because-non-exported-macro)
95/// let letters = map!{"a" => "b", "c" => "d"};
96/// ```
97///
98/// Trailing commas are allowed.
99/// Commas between elements are required (even if the expression is a block).
100macro_rules! map {
101    ($( $key: expr => $val: expr ),* $(,)*) => {{
102        let mut map = ::rustc_data_structures::fx::FxIndexMap::default();
103        $( map.insert($key, $val); )*
104        map
105    }}
106}
107
108mod clean;
109mod config;
110mod core;
111mod display;
112mod docfs;
113mod doctest;
114mod error;
115mod externalfiles;
116mod fold;
117mod formats;
118// used by the error-index generator, so it needs to be public
119pub mod html;
120mod json;
121pub(crate) mod lint;
122mod markdown;
123mod passes;
124mod scrape_examples;
125mod theme;
126mod visit;
127mod visit_ast;
128mod visit_lib;
129
130pub fn main() {
131    let mut early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
132
133    rustc_driver::install_ice_hook(
134        "https://github.com/rust-lang/rust/issues/new\
135    ?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md",
136        |_| (),
137    );
138
139    // When using CI artifacts with `download-rustc`, tracing is unconditionally built
140    // with `--features=static_max_level_info`, which disables almost all rustdoc logging. To avoid
141    // this, compile our own version of `tracing` that logs all levels.
142    // NOTE: this compiles both versions of tracing unconditionally, because
143    // - The compile time hit is not that bad, especially compared to rustdoc's incremental times, and
144    // - Otherwise, there's no warning that logging is being ignored when `download-rustc` is enabled
145
146    crate::init_logging(&early_dcx);
147    match rustc_log::init_logger(rustc_log::LoggerConfig::from_env("RUSTDOC_LOG")) {
148        Ok(()) => {}
149        // With `download-rustc = true` there are definitely 2 distinct tracing crates in the
150        // dependency graph: one in the downloaded sysroot and one built just now as a dependency of
151        // rustdoc. So the sysroot's tracing is definitely not yet initialized here.
152        //
153        // But otherwise, depending on link style, there may or may not be 2 tracing crates in play.
154        // The one we just initialized in `crate::init_logging` above is rustdoc's direct dependency
155        // on tracing. When rustdoc is built by x.py using Cargo, rustc_driver's and rustc_log's
156        // tracing dependency is distinct from this one and also needs to be initialized (using the
157        // same RUSTDOC_LOG environment variable for both). Other build systems may use just a
158        // single tracing crate throughout the rustc and rustdoc build.
159        //
160        // The reason initializing 2 tracings does not show double logging when `download-rustc =
161        // false` and `debug_logging = true` is because all rustc logging goes only to its version
162        // of tracing (the one in the sysroot) and all of rustdoc's logging only goes to its version
163        // (the one in Cargo.toml).
164        Err(rustc_log::Error::AlreadyInit(_)) => {}
165        Err(error) => early_dcx.early_fatal(error.to_string()),
166    }
167
168    let exit_code = rustc_driver::catch_with_exit_code(|| {
169        let at_args = rustc_driver::args::raw_args(&early_dcx);
170        main_args(&mut early_dcx, &at_args);
171    });
172    process::exit(exit_code);
173}
174
175fn init_logging(early_dcx: &EarlyDiagCtxt) {
176    let color_logs = match env::var("RUSTDOC_LOG_COLOR").as_deref() {
177        Ok("always") => true,
178        Ok("never") => false,
179        Ok("auto") | Err(VarError::NotPresent) => io::stdout().is_terminal(),
180        Ok(value) => early_dcx.early_fatal(format!(
181            "invalid log color value '{value}': expected one of always, never, or auto",
182        )),
183        Err(VarError::NotUnicode(value)) => early_dcx.early_fatal(format!(
184            "invalid log color value '{}': expected one of always, never, or auto",
185            value.to_string_lossy()
186        )),
187    };
188    let filter = tracing_subscriber::EnvFilter::from_env("RUSTDOC_LOG");
189    let layer = tracing_tree::HierarchicalLayer::default()
190        .with_writer(io::stderr)
191        .with_ansi(color_logs)
192        .with_targets(true)
193        .with_wraparound(10)
194        .with_verbose_exit(true)
195        .with_verbose_entry(true)
196        .with_indent_amount(2);
197    #[cfg(debug_assertions)]
198    let layer = layer.with_thread_ids(true).with_thread_names(true);
199
200    use tracing_subscriber::layer::SubscriberExt;
201    let subscriber = tracing_subscriber::Registry::default().with(filter).with(layer);
202    tracing::subscriber::set_global_default(subscriber).unwrap();
203}
204
205fn opts() -> Vec<RustcOptGroup> {
206    use rustc_session::config::OptionKind::{Flag, FlagMulti, Multi, Opt};
207    use rustc_session::config::OptionStability::{Stable, Unstable};
208    use rustc_session::config::make_opt as opt;
209
210    vec![
211        opt(Stable, FlagMulti, "h", "help", "show this help message", ""),
212        opt(Stable, FlagMulti, "V", "version", "print rustdoc's version", ""),
213        opt(Stable, FlagMulti, "v", "verbose", "use verbose output", ""),
214        opt(Stable, Opt, "w", "output-format", "the output type to write", "[html]"),
215        opt(
216            Stable,
217            Opt,
218            "",
219            "output",
220            "Which directory to place the output. This option is deprecated, use --out-dir instead.",
221            "PATH",
222        ),
223        opt(Stable, Opt, "o", "out-dir", "which directory to place the output", "PATH"),
224        opt(Stable, Opt, "", "crate-name", "specify the name of this crate", "NAME"),
225        make_crate_type_option(),
226        opt(Stable, Multi, "L", "library-path", "directory to add to crate search path", "DIR"),
227        opt(Stable, Multi, "", "cfg", "pass a --cfg to rustc", ""),
228        opt(Stable, Multi, "", "check-cfg", "pass a --check-cfg to rustc", ""),
229        opt(Stable, Multi, "", "extern", "pass an --extern to rustc", "NAME[=PATH]"),
230        opt(
231            Unstable,
232            Multi,
233            "",
234            "extern-html-root-url",
235            "base URL to use for dependencies; for example, \
236                \"std=/doc\" links std::vec::Vec to /doc/std/vec/struct.Vec.html",
237            "NAME=URL",
238        ),
239        opt(
240            Unstable,
241            FlagMulti,
242            "",
243            "extern-html-root-takes-precedence",
244            "give precedence to `--extern-html-root-url`, not `html_root_url`",
245            "",
246        ),
247        opt(Stable, Multi, "C", "codegen", "pass a codegen option to rustc", "OPT[=VALUE]"),
248        opt(Stable, FlagMulti, "", "document-private-items", "document private items", ""),
249        opt(
250            Unstable,
251            FlagMulti,
252            "",
253            "document-hidden-items",
254            "document items that have doc(hidden)",
255            "",
256        ),
257        opt(Stable, FlagMulti, "", "test", "run code examples as tests", ""),
258        opt(Stable, Multi, "", "test-args", "arguments to pass to the test runner", "ARGS"),
259        opt(
260            Stable,
261            Opt,
262            "",
263            "test-run-directory",
264            "The working directory in which to run tests",
265            "PATH",
266        ),
267        opt(Stable, Opt, "", "target", "target triple to document", "TRIPLE"),
268        opt(
269            Stable,
270            Multi,
271            "",
272            "markdown-css",
273            "CSS files to include via <link> in a rendered Markdown file",
274            "FILES",
275        ),
276        opt(
277            Stable,
278            Multi,
279            "",
280            "html-in-header",
281            "files to include inline in the <head> section of a rendered Markdown file \
282                or generated documentation",
283            "FILES",
284        ),
285        opt(
286            Stable,
287            Multi,
288            "",
289            "html-before-content",
290            "files to include inline between <body> and the content of a rendered \
291                Markdown file or generated documentation",
292            "FILES",
293        ),
294        opt(
295            Stable,
296            Multi,
297            "",
298            "html-after-content",
299            "files to include inline between the content and </body> of a rendered \
300                Markdown file or generated documentation",
301            "FILES",
302        ),
303        opt(
304            Unstable,
305            Multi,
306            "",
307            "markdown-before-content",
308            "files to include inline between <body> and the content of a rendered \
309                Markdown file or generated documentation",
310            "FILES",
311        ),
312        opt(
313            Unstable,
314            Multi,
315            "",
316            "markdown-after-content",
317            "files to include inline between the content and </body> of a rendered \
318                Markdown file or generated documentation",
319            "FILES",
320        ),
321        opt(Stable, Opt, "", "markdown-playground-url", "URL to send code snippets to", "URL"),
322        opt(Stable, FlagMulti, "", "markdown-no-toc", "don't include table of contents", ""),
323        opt(
324            Stable,
325            Opt,
326            "e",
327            "extend-css",
328            "To add some CSS rules with a given file to generate doc with your own theme. \
329                However, your theme might break if the rustdoc's generated HTML changes, so be careful!",
330            "PATH",
331        ),
332        opt(
333            Unstable,
334            Multi,
335            "Z",
336            "",
337            "unstable / perma-unstable options (only on nightly build)",
338            "FLAG",
339        ),
340        opt(Stable, Opt, "", "sysroot", "Override the system root", "PATH"),
341        opt(
342            Unstable,
343            Opt,
344            "",
345            "playground-url",
346            "URL to send code snippets to, may be reset by --markdown-playground-url \
347                or `#![doc(html_playground_url=...)]`",
348            "URL",
349        ),
350        opt(
351            Unstable,
352            FlagMulti,
353            "",
354            "display-doctest-warnings",
355            "show warnings that originate in doctests",
356            "",
357        ),
358        opt(
359            Stable,
360            Opt,
361            "",
362            "crate-version",
363            "crate version to print into documentation",
364            "VERSION",
365        ),
366        opt(
367            Unstable,
368            FlagMulti,
369            "",
370            "sort-modules-by-appearance",
371            "sort modules by where they appear in the program, rather than alphabetically",
372            "",
373        ),
374        opt(
375            Stable,
376            Opt,
377            "",
378            "default-theme",
379            "Set the default theme. THEME should be the theme name, generally lowercase. \
380                If an unknown default theme is specified, the builtin default is used. \
381                The set of themes, and the rustdoc built-in default, are not stable.",
382            "THEME",
383        ),
384        opt(
385            Unstable,
386            Multi,
387            "",
388            "default-setting",
389            "Default value for a rustdoc setting (used when \"rustdoc-SETTING\" is absent \
390                from web browser Local Storage). If VALUE is not supplied, \"true\" is used. \
391                Supported SETTINGs and VALUEs are not documented and not stable.",
392            "SETTING[=VALUE]",
393        ),
394        opt(
395            Stable,
396            Multi,
397            "",
398            "theme",
399            "additional themes which will be added to the generated docs",
400            "FILES",
401        ),
402        opt(Stable, Multi, "", "check-theme", "check if given theme is valid", "FILES"),
403        opt(
404            Unstable,
405            Opt,
406            "",
407            "resource-suffix",
408            "suffix to add to CSS and JavaScript files, \
409                e.g., \"search-index.js\" will become \"search-index-suffix.js\"",
410            "PATH",
411        ),
412        opt(
413            Stable,
414            Opt,
415            "",
416            "edition",
417            "edition to use when compiling rust code (default: 2015)",
418            "EDITION",
419        ),
420        opt(
421            Stable,
422            Opt,
423            "",
424            "color",
425            "Configure coloring of output:
426                                          auto   = colorize, if output goes to a tty (default);
427                                          always = always colorize output;
428                                          never  = never colorize output",
429            "auto|always|never",
430        ),
431        opt(
432            Stable,
433            Opt,
434            "",
435            "error-format",
436            "How errors and other messages are produced",
437            "human|json|short",
438        ),
439        opt(
440            Stable,
441            Opt,
442            "",
443            "diagnostic-width",
444            "Provide width of the output for truncated error messages",
445            "WIDTH",
446        ),
447        opt(Stable, Opt, "", "json", "Configure the structure of JSON diagnostics", "CONFIG"),
448        opt(Stable, Multi, "A", "allow", "Set lint allowed", "LINT"),
449        opt(Stable, Multi, "W", "warn", "Set lint warnings", "LINT"),
450        opt(Stable, Multi, "", "force-warn", "Set lint force-warn", "LINT"),
451        opt(Stable, Multi, "D", "deny", "Set lint denied", "LINT"),
452        opt(Stable, Multi, "F", "forbid", "Set lint forbidden", "LINT"),
453        opt(
454            Stable,
455            Multi,
456            "",
457            "cap-lints",
458            "Set the most restrictive lint level. \
459                More restrictive lints are capped at this level. \
460                By default, it is at `forbid` level.",
461            "LEVEL",
462        ),
463        opt(Unstable, Opt, "", "index-page", "Markdown file to be used as index page", "PATH"),
464        opt(
465            Unstable,
466            FlagMulti,
467            "",
468            "enable-index-page",
469            "To enable generation of the index page",
470            "",
471        ),
472        opt(
473            Unstable,
474            Opt,
475            "",
476            "static-root-path",
477            "Path string to force loading static files from in output pages. \
478                If not set, uses combinations of '../' to reach the documentation root.",
479            "PATH",
480        ),
481        opt(
482            Unstable,
483            Opt,
484            "",
485            "persist-doctests",
486            "Directory to persist doctest executables into",
487            "PATH",
488        ),
489        opt(
490            Unstable,
491            FlagMulti,
492            "",
493            "show-coverage",
494            "calculate percentage of public items with documentation",
495            "",
496        ),
497        opt(
498            Stable,
499            Opt,
500            "",
501            "test-runtool",
502            "",
503            "The tool to run tests with when building for a different target than host",
504        ),
505        opt(
506            Stable,
507            Multi,
508            "",
509            "test-runtool-arg",
510            "",
511            "One argument (of possibly many) to pass to the runtool",
512        ),
513        opt(
514            Unstable,
515            Opt,
516            "",
517            "test-builder",
518            "The rustc-like binary to use as the test builder",
519            "PATH",
520        ),
521        opt(
522            Unstable,
523            Multi,
524            "",
525            "test-builder-wrapper",
526            "Wrapper program to pass test-builder and arguments",
527            "PATH",
528        ),
529        opt(Unstable, FlagMulti, "", "check", "Run rustdoc checks", ""),
530        opt(
531            Unstable,
532            FlagMulti,
533            "",
534            "generate-redirect-map",
535            "Generate JSON file at the top level instead of generating HTML redirection files",
536            "",
537        ),
538        opt(
539            Unstable,
540            Multi,
541            "",
542            "emit",
543            "Comma separated list of types of output for rustdoc to emit",
544            "[toolchain-shared-resources,invocation-specific,dep-info]",
545        ),
546        opt(Unstable, FlagMulti, "", "no-run", "Compile doctests without running them", ""),
547        opt(
548            Unstable,
549            Opt,
550            "",
551            "merge-doctests",
552            "Force all doctests to be compiled as a single binary, instead of one binary per test. If merging fails, rustdoc will emit a hard error.",
553            "yes|no|auto",
554        ),
555        opt(
556            Unstable,
557            Multi,
558            "",
559            "remap-path-prefix",
560            "Remap source names in compiler messages",
561            "FROM=TO",
562        ),
563        opt(
564            Unstable,
565            FlagMulti,
566            "",
567            "show-type-layout",
568            "Include the memory layout of types in the docs",
569            "",
570        ),
571        opt(Unstable, Flag, "", "no-capture", "Don't capture stdout and stderr of tests", ""),
572        opt(
573            Unstable,
574            Flag,
575            "",
576            "generate-link-to-definition",
577            "Make the identifiers in the HTML source code pages navigable",
578            "",
579        ),
580        opt(
581            Unstable,
582            Opt,
583            "",
584            "scrape-examples-output-path",
585            "",
586            "collect function call information and output at the given path",
587        ),
588        opt(
589            Unstable,
590            Multi,
591            "",
592            "scrape-examples-target-crate",
593            "",
594            "collect function call information for functions from the target crate",
595        ),
596        opt(Unstable, Flag, "", "scrape-tests", "Include test code when scraping examples", ""),
597        opt(
598            Unstable,
599            Multi,
600            "",
601            "with-examples",
602            "",
603            "path to function call information (for displaying examples in the documentation)",
604        ),
605        opt(
606            Unstable,
607            Opt,
608            "",
609            "merge",
610            "Controls how rustdoc handles files from previously documented crates in the doc root\n\
611                none = Do not write cross-crate information to the --out-dir\n\
612                shared = Append current crate's info to files found in the --out-dir\n\
613                finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
614            "none|shared|finalize",
615        ),
616        opt(
617            Unstable,
618            Opt,
619            "",
620            "parts-out-dir",
621            "Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
622            "path/to/doc.parts/<crate-name>",
623        ),
624        opt(
625            Unstable,
626            Multi,
627            "",
628            "include-parts-dir",
629            "Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
630            "path/to/doc.parts/<crate-name>",
631        ),
632        opt(Unstable, Flag, "", "html-no-source", "Disable HTML source code pages generation", ""),
633        opt(
634            Unstable,
635            Multi,
636            "",
637            "doctest-build-arg",
638            "One argument (of possibly many) to be used when compiling doctests",
639            "ARG",
640        ),
641        opt(
642            Unstable,
643            FlagMulti,
644            "",
645            "disable-minification",
646            "disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
647            "",
648        ),
649        opt(
650            Unstable,
651            Flag,
652            "",
653            "generate-macro-expansion",
654            "Add possibility to expand macros in the HTML source code pages",
655            "",
656        ),
657        // deprecated / removed options
658        opt(
659            Stable,
660            Multi,
661            "",
662            "plugin-path",
663            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
664            "DIR",
665        ),
666        opt(
667            Stable,
668            Multi,
669            "",
670            "passes",
671            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
672            "PASSES",
673        ),
674        opt(
675            Stable,
676            Multi,
677            "",
678            "plugins",
679            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
680            "PLUGINS",
681        ),
682        opt(
683            Stable,
684            FlagMulti,
685            "",
686            "no-defaults",
687            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
688            "",
689        ),
690        opt(
691            Stable,
692            Opt,
693            "r",
694            "input-format",
695            "removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
696            "[rust]",
697        ),
698    ]
699}
700
701fn usage(argv0: &str) {
702    let mut options = getopts::Options::new();
703    for option in opts() {
704        option.apply(&mut options);
705    }
706    println!("{}", options.usage(&format!("{argv0} [options] <input>")));
707    println!("    @path               Read newline separated options from `path`\n");
708    println!(
709        "More information available at {DOC_RUST_LANG_ORG_VERSION}/rustdoc/what-is-rustdoc.html",
710    );
711}
712
713pub(crate) fn wrap_return(dcx: DiagCtxtHandle<'_>, res: Result<(), String>) {
714    match res {
715        Ok(()) => dcx.abort_if_errors(),
716        Err(err) => dcx.fatal(err),
717    }
718}
719
720fn run_renderer<
721    'tcx,
722    T: formats::FormatRenderer<'tcx>,
723    F: FnOnce(
724        clean::Crate,
725        config::RenderOptions,
726        Cache,
727        TyCtxt<'tcx>,
728    ) -> Result<(T, clean::Crate), Error>,
729>(
730    krate: clean::Crate,
731    renderopts: config::RenderOptions,
732    cache: formats::cache::Cache,
733    tcx: TyCtxt<'tcx>,
734    init: F,
735) {
736    match formats::run_format::<T, F>(krate, renderopts, cache, tcx, init) {
737        Ok(_) => tcx.dcx().abort_if_errors(),
738        Err(e) => {
739            let mut msg =
740                tcx.dcx().struct_fatal(format!("couldn't generate documentation: {}", e.error));
741            let file = e.file.display().to_string();
742            if !file.is_empty() {
743                msg.note(format!("failed to create or modify {e}"));
744            } else {
745                msg.note(format!("failed to create or modify file: {e}"));
746            }
747            msg.emit();
748        }
749    }
750}
751
752/// Renders and writes cross-crate info files, like the search index. This function exists so that
753/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
754/// discovered via `--include-parts-dir` are combined and written to the doc root.
755fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
756    assert!(
757        opt.should_merge.write_rendered_cci,
758        "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
759    );
760    assert!(
761        !opt.should_merge.read_rendered_cci,
762        "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
763    );
764    let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?;
765    let include_sources = !opt.html_no_source;
766    html::render::write_not_crate_specific(
767        &crates,
768        &opt.output,
769        &opt,
770        &opt.themes,
771        opt.extension_css.as_deref(),
772        &opt.resource_suffix,
773        include_sources,
774    )?;
775    Ok(())
776}
777
778fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
779    // Throw away the first argument, the name of the binary.
780    // In case of at_args being empty, as might be the case by
781    // passing empty argument array to execve under some platforms,
782    // just use an empty slice.
783    //
784    // This situation was possible before due to arg_expand_all being
785    // called before removing the argument, enabling a crash by calling
786    // the compiler with @empty_file as argv[0] and no more arguments.
787    let at_args = at_args.get(1..).unwrap_or_default();
788
789    let args = rustc_driver::args::arg_expand_all(early_dcx, at_args);
790
791    let mut options = getopts::Options::new();
792    for option in opts() {
793        option.apply(&mut options);
794    }
795    let matches = match options.parse(&args) {
796        Ok(m) => m,
797        Err(err) => {
798            early_dcx.early_fatal(err.to_string());
799        }
800    };
801
802    // Note that we discard any distinction between different non-zero exit
803    // codes from `from_matches` here.
804    let (input, options, render_options, loaded_paths) =
805        match config::Options::from_matches(early_dcx, &matches, args) {
806            Some(opts) => opts,
807            None => return,
808        };
809
810    let dcx =
811        core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
812    let dcx = dcx.handle();
813
814    let input = match input {
815        config::InputMode::HasFile(input) => input,
816        config::InputMode::NoInputMergeFinalize => {
817            return wrap_return(
818                dcx,
819                rustc_span::create_session_globals_then(options.edition, &[], None, || {
820                    run_merge_finalize(render_options)
821                        .map_err(|e| format!("could not write merged cross-crate info: {e}"))
822                }),
823            );
824        }
825    };
826
827    let output_format = options.output_format;
828
829    match (
830        options.should_test || output_format == config::OutputFormat::Doctest,
831        config::markdown_input(&input),
832    ) {
833        (true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options, dcx)),
834        (true, None) => return doctest::run(dcx, input, options),
835        (false, Some(md_input)) => {
836            let md_input = md_input.to_owned();
837            let edition = options.edition;
838            let config = core::create_config(input, options, &render_options);
839
840            // `markdown::render` can invoke `doctest::make_test`, which
841            // requires session globals and a thread pool, so we use
842            // `run_compiler`.
843            return wrap_return(
844                dcx,
845                interface::run_compiler(config, |_compiler| {
846                    markdown::render_and_write(&md_input, render_options, edition)
847                }),
848            );
849        }
850        (false, None) => {}
851    }
852
853    // need to move these items separately because we lose them by the time the closure is called,
854    // but we can't create the dcx ahead of time because it's not Send
855    let show_coverage = options.show_coverage;
856    let run_check = options.run_check;
857
858    // First, parse the crate and extract all relevant information.
859    info!("starting to run rustc");
860
861    // Interpret the input file as a rust source file, passing it through the
862    // compiler all the way through the analysis passes. The rustdoc output is
863    // then generated from the cleaned AST of the crate. This runs all the
864    // plug/cleaning passes.
865    let crate_version = options.crate_version.clone();
866
867    let scrape_examples_options = options.scrape_examples_options.clone();
868    let bin_crate = options.bin_crate;
869
870    let output_format = options.output_format;
871    let config = core::create_config(input, options, &render_options);
872
873    let registered_lints = config.register_lints.is_some();
874
875    interface::run_compiler(config, |compiler| {
876        let sess = &compiler.sess;
877
878        // Register the loaded external files in the source map so they show up in depinfo.
879        // We can't load them via the source map because it gets created after we process the options.
880        for external_path in &loaded_paths {
881            let _ = sess.source_map().load_binary_file(external_path);
882        }
883
884        if sess.opts.describe_lints {
885            rustc_driver::describe_lints(sess, registered_lints);
886            return;
887        }
888
889        let krate = rustc_interface::passes::parse(sess);
890        rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| {
891            if sess.dcx().has_errors().is_some() {
892                sess.dcx().fatal("Compilation failed, aborting rustdoc");
893            }
894
895            let (krate, render_opts, mut cache, expanded_macros) = sess
896                .time("run_global_ctxt", || {
897                    core::run_global_ctxt(tcx, show_coverage, render_options, output_format)
898                });
899            info!("finished with rustc");
900
901            if let Some(options) = scrape_examples_options {
902                return scrape_examples::run(krate, render_opts, cache, tcx, options, bin_crate);
903            }
904
905            cache.crate_version = crate_version;
906
907            if show_coverage {
908                // if we ran coverage, bail early, we don't need to also generate docs at this point
909                // (also we didn't load in any of the useful passes)
910                return;
911            }
912
913            for owner_id in tcx.hir_crate_items(()).delayed_lint_items() {
914                if let Some(delayed_lints) = tcx.opt_ast_lowering_delayed_lints(owner_id) {
915                    for lint in &delayed_lints.lints {
916                        match lint {
917                            DelayedLint::AttributeParsing(attribute_lint) => {
918                                tcx.node_span_lint(
919                                    attribute_lint.lint_id.lint,
920                                    attribute_lint.id,
921                                    attribute_lint.span,
922                                    |diag| {
923                                        rustc_lint::decorate_attribute_lint(
924                                            tcx.sess,
925                                            Some(tcx),
926                                            &attribute_lint.kind,
927                                            diag,
928                                        );
929                                    },
930                                );
931                            }
932                        }
933                    }
934                }
935            }
936
937            if render_opts.dep_info().is_some() {
938                rustc_interface::passes::write_dep_info(tcx);
939            }
940
941            if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir {
942                dump_feature_usage_metrics(tcx, metrics_dir);
943            }
944
945            if run_check {
946                // Since we're in "check" mode, no need to generate anything beyond this point.
947                return;
948            }
949
950            info!("going to format");
951            match output_format {
952                config::OutputFormat::Html => sess.time("render_html", || {
953                    run_renderer(
954                        krate,
955                        render_opts,
956                        cache,
957                        tcx,
958                        |krate, render_opts, cache, tcx| {
959                            html::render::Context::init(
960                                krate,
961                                render_opts,
962                                cache,
963                                tcx,
964                                expanded_macros,
965                            )
966                        },
967                    )
968                }),
969                config::OutputFormat::Json => sess.time("render_json", || {
970                    run_renderer(krate, render_opts, cache, tcx, json::JsonRenderer::init)
971                }),
972                // Already handled above with doctest runners.
973                config::OutputFormat::Doctest => unreachable!(),
974            }
975        })
976    })
977}
978
979fn dump_feature_usage_metrics(tcxt: TyCtxt<'_>, metrics_dir: &Path) {
980    let hash = tcxt.crate_hash(LOCAL_CRATE);
981    let crate_name = tcxt.crate_name(LOCAL_CRATE);
982    let metrics_file_name = format!("unstable_feature_usage_metrics-{crate_name}-{hash}.json");
983    let metrics_path = metrics_dir.join(metrics_file_name);
984    if let Err(error) = tcxt.features().dump_feature_usage_metrics(metrics_path) {
985        // FIXME(yaahc): once metrics can be enabled by default we will want "failure to emit
986        // default metrics" to only produce a warning when metrics are enabled by default and emit
987        // an error only when the user manually enables metrics
988        tcxt.dcx().err(format!("cannot emit feature usage metrics: {error}"));
989    }
990}