Skip to main content

rustdoc/
lib.rs

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