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