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