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