bootstrap/core/config/
flags.rs

1//! Command-line interface of the bootstrap build system.
2//!
3//! This module implements the command-line parsing of the build system which
4//! has various flags to configure how it's run.
5
6use std::path::{Path, PathBuf};
7
8use clap::{CommandFactory, Parser, ValueEnum};
9#[cfg(feature = "tracing")]
10use tracing::instrument;
11
12use crate::core::build_steps::perf::PerfArgs;
13use crate::core::build_steps::setup::Profile;
14use crate::core::builder::{Builder, Kind};
15use crate::core::config::{Config, TargetSelectionList, target_selection_list};
16use crate::{Build, DocTests};
17
18#[derive(Copy, Clone, Default, Debug, ValueEnum)]
19pub enum Color {
20    Always,
21    Never,
22    #[default]
23    Auto,
24}
25
26/// Whether to deny warnings, emit them as warnings, or use the default behavior
27#[derive(Copy, Clone, Default, Debug, ValueEnum)]
28pub enum Warnings {
29    Deny,
30    Warn,
31    #[default]
32    Default,
33}
34
35/// Deserialized version of all flags for this compile.
36#[derive(Debug, Parser)]
37#[command(
38    override_usage = "x.py <subcommand> [options] [<paths>...]",
39    disable_help_subcommand(true),
40    about = "",
41    next_line_help(false)
42)]
43pub struct Flags {
44    #[command(subcommand)]
45    pub cmd: Subcommand,
46
47    #[arg(global = true, short, long, action = clap::ArgAction::Count)]
48    /// use verbose output (-vv for very verbose)
49    pub verbose: u8, // each extra -v after the first is passed to Cargo
50    #[arg(global = true, short, long)]
51    /// use incremental compilation
52    pub incremental: bool,
53    #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
54    /// TOML configuration file for build
55    pub config: Option<PathBuf>,
56    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
57    /// Build directory, overrides `build.build-dir` in `bootstrap.toml`
58    pub build_dir: Option<PathBuf>,
59
60    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
61    /// build target of the stage0 compiler
62    pub build: Option<String>,
63
64    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
65    /// host targets to build
66    pub host: Option<TargetSelectionList>,
67
68    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
69    /// target targets to build
70    pub target: Option<TargetSelectionList>,
71
72    #[arg(global = true, long, value_name = "PATH")]
73    /// build paths to exclude
74    pub exclude: Vec<PathBuf>, // keeping for client backward compatibility
75    #[arg(global = true, long, value_name = "PATH")]
76    /// build paths to skip
77    pub skip: Vec<PathBuf>,
78    #[arg(global = true, long)]
79    /// include default paths in addition to the provided ones
80    pub include_default_paths: bool,
81
82    #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
83    pub rustc_error_format: Option<String>,
84
85    #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
86    /// command to run on failure
87    pub on_fail: Option<String>,
88    #[arg(global = true, long)]
89    /// dry run; don't build anything
90    pub dry_run: bool,
91    /// Indicates whether to dump the work done from bootstrap shims
92    #[arg(global = true, long)]
93    pub dump_bootstrap_shims: bool,
94    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
95    /// stage to build (indicates compiler to use/test, e.g., stage 0 uses the
96    /// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)
97    pub stage: Option<u32>,
98
99    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
100    /// stage(s) to keep without recompiling
101    /// (pass multiple times to keep e.g., both stages 0 and 1)
102    pub keep_stage: Vec<u32>,
103    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
104    /// stage(s) of the standard library to keep without recompiling
105    /// (pass multiple times to keep e.g., both stages 0 and 1)
106    pub keep_stage_std: Vec<u32>,
107    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
108    /// path to the root of the rust checkout
109    pub src: Option<PathBuf>,
110
111    #[arg(
112        global = true,
113        short,
114        long,
115        value_hint = clap::ValueHint::Other,
116        value_name = "JOBS"
117    )]
118    /// number of jobs to run in parallel
119    pub jobs: Option<u32>,
120    // This overrides the deny-warnings configuration option,
121    // which passes -Dwarnings to the compiler invocations.
122    #[arg(global = true, long)]
123    #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
124    /// if value is deny, will deny warnings
125    /// if value is warn, will emit warnings
126    /// otherwise, use the default configured behaviour
127    pub warnings: Warnings,
128
129    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
130    /// rustc error format
131    pub error_format: Option<String>,
132    #[arg(global = true, long)]
133    /// use message-format=json
134    pub json_output: bool,
135
136    #[arg(global = true, long, value_name = "STYLE")]
137    #[arg(value_enum, default_value_t = Color::Auto)]
138    /// whether to use color in cargo and rustc output
139    pub color: Color,
140
141    #[arg(global = true, long)]
142    /// Bootstrap uses this value to decide whether it should bypass locking the build process.
143    /// This is rarely needed (e.g., compiling the std library for different targets in parallel).
144    ///
145    /// Unless you know exactly what you are doing, you probably don't need this.
146    pub bypass_bootstrap_lock: bool,
147
148    /// generate PGO profile with rustc build
149    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
150    pub rust_profile_generate: Option<String>,
151    /// use PGO profile for rustc build
152    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
153    pub rust_profile_use: Option<String>,
154    /// use PGO profile for LLVM build
155    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
156    pub llvm_profile_use: Option<String>,
157    // LLVM doesn't support a custom location for generating profile
158    // information.
159    //
160    // llvm_out/build/profiles/ is the location this writes to.
161    /// generate PGO profile with llvm built for rustc
162    #[arg(global = true, long)]
163    pub llvm_profile_generate: bool,
164    /// Enable BOLT link flags
165    #[arg(global = true, long)]
166    pub enable_bolt_settings: bool,
167    /// Skip stage0 compiler validation
168    #[arg(global = true, long)]
169    pub skip_stage0_validation: bool,
170    /// Additional reproducible artifacts that should be added to the reproducible artifacts archive.
171    #[arg(global = true, long)]
172    pub reproducible_artifact: Vec<String>,
173    #[arg(global = true)]
174    /// paths for the subcommand
175    pub paths: Vec<PathBuf>,
176    /// override options in bootstrap.toml
177    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
178    pub set: Vec<String>,
179    /// arguments passed to subcommands
180    #[arg(global = true, last(true), value_name = "ARGS")]
181    pub free_args: Vec<String>,
182    /// Make bootstrap to behave as it's running on the CI environment or not.
183    #[arg(global = true, long, value_name = "bool")]
184    pub ci: Option<bool>,
185}
186
187impl Flags {
188    /// Check if `<cmd> -h -v` was passed.
189    /// If yes, print the available paths and return `true`.
190    pub fn try_parse_verbose_help(args: &[String]) -> bool {
191        // We need to check for `<cmd> -h -v`, in which case we list the paths
192        #[derive(Parser)]
193        #[command(disable_help_flag(true))]
194        struct HelpVerboseOnly {
195            #[arg(short, long)]
196            help: bool,
197            #[arg(global = true, short, long, action = clap::ArgAction::Count)]
198            pub verbose: u8,
199            #[arg(value_enum)]
200            cmd: Kind,
201        }
202        if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
203            HelpVerboseOnly::try_parse_from(normalize_args(args))
204        {
205            println!("NOTE: updating submodules before printing available paths");
206            let config = Config::parse(Self::parse(&[String::from("build")]));
207            let build = Build::new(config);
208            let paths = Builder::get_help(&build, subcommand);
209            if let Some(s) = paths {
210                println!("{s}");
211            } else {
212                panic!("No paths available for subcommand `{}`", subcommand.as_str());
213            }
214            true
215        } else {
216            false
217        }
218    }
219
220    #[cfg_attr(
221        feature = "tracing",
222        instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
223    )]
224    pub fn parse(args: &[String]) -> Self {
225        Flags::parse_from(normalize_args(args))
226    }
227}
228
229fn normalize_args(args: &[String]) -> Vec<String> {
230    let first = String::from("x.py");
231    let it = std::iter::once(first).chain(args.iter().cloned());
232    it.collect()
233}
234
235#[derive(Debug, Clone, Default, clap::Subcommand)]
236pub enum Subcommand {
237    #[command(aliases = ["b"], long_about = "\n
238    Arguments:
239        This subcommand accepts a number of paths to directories to the crates
240        and/or artifacts to compile. For example, for a quick build of a usable
241        compiler:
242            ./x.py build --stage 1 library/std
243        This will build a compiler and standard library from the local source code.
244        Once this is done, build/$ARCH/stage1 contains a usable compiler.
245        If no arguments are passed then the default artifacts for that stage are
246        compiled. For example:
247            ./x.py build --stage 0
248            ./x.py build ")]
249    /// Compile either the compiler or libraries
250    #[default]
251    Build,
252    #[command(aliases = ["c"], long_about = "\n
253    Arguments:
254        This subcommand accepts a number of paths to directories to the crates
255        and/or artifacts to compile. For example:
256            ./x.py check library/std
257        If no arguments are passed then many artifacts are checked.")]
258    /// Compile either the compiler or libraries, using cargo check
259    Check {
260        #[arg(long)]
261        /// Check all targets
262        all_targets: bool,
263    },
264    /// Run Clippy (uses rustup/cargo-installed clippy binary)
265    #[command(long_about = "\n
266    Arguments:
267        This subcommand accepts a number of paths to directories to the crates
268        and/or artifacts to run clippy against. For example:
269            ./x.py clippy library/core
270            ./x.py clippy library/core library/proc_macro")]
271    Clippy {
272        #[arg(long)]
273        fix: bool,
274        #[arg(long, requires = "fix")]
275        allow_dirty: bool,
276        #[arg(long, requires = "fix")]
277        allow_staged: bool,
278        /// clippy lints to allow
279        #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
280        allow: Vec<String>,
281        /// clippy lints to deny
282        #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
283        deny: Vec<String>,
284        /// clippy lints to warn on
285        #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
286        warn: Vec<String>,
287        /// clippy lints to forbid
288        #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
289        forbid: Vec<String>,
290    },
291    /// Run cargo fix
292    #[command(long_about = "\n
293    Arguments:
294        This subcommand accepts a number of paths to directories to the crates
295        and/or artifacts to run `cargo fix` against. For example:
296            ./x.py fix library/core
297            ./x.py fix library/core library/proc_macro")]
298    Fix,
299    #[command(
300        name = "fmt",
301        long_about = "\n
302    Arguments:
303        This subcommand optionally accepts a `--check` flag which succeeds if
304        formatting is correct and fails if it is not. For example:
305            ./x.py fmt
306            ./x.py fmt --check"
307    )]
308    /// Run rustfmt
309    Format {
310        /// check formatting instead of applying
311        #[arg(long)]
312        check: bool,
313
314        /// apply to all appropriate files, not just those that have been modified
315        #[arg(long)]
316        all: bool,
317    },
318    #[command(aliases = ["d"], long_about = "\n
319    Arguments:
320        This subcommand accepts a number of paths to directories of documentation
321        to build. For example:
322            ./x.py doc src/doc/book
323            ./x.py doc src/doc/nomicon
324            ./x.py doc src/doc/book library/std
325            ./x.py doc library/std --json
326            ./x.py doc library/std --open
327        If no arguments are passed then everything is documented:
328            ./x.py doc
329            ./x.py doc --stage 1")]
330    /// Build documentation
331    Doc {
332        #[arg(long)]
333        /// open the docs in a browser
334        open: bool,
335        #[arg(long)]
336        /// render the documentation in JSON format in addition to the usual HTML format
337        json: bool,
338    },
339    #[command(aliases = ["t"], long_about = "\n
340    Arguments:
341        This subcommand accepts a number of paths to test directories that
342        should be compiled and run. For example:
343            ./x.py test tests/ui
344            ./x.py test library/std --test-args hash_map
345            ./x.py test library/std --stage 0 --no-doc
346            ./x.py test tests/ui --bless
347            ./x.py test tests/ui --compare-mode next-solver
348        Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
349        just like `build library/std --stage N` it tests the compiler produced by the previous
350        stage.
351        Execute tool tests with a tool name argument:
352            ./x.py test tidy
353        If no arguments are passed then the complete artifacts for that stage are
354        compiled and tested.
355            ./x.py test
356            ./x.py test --stage 1")]
357    /// Build and run some test suites
358    Test {
359        #[arg(long)]
360        /// run all tests regardless of failure
361        no_fail_fast: bool,
362        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
363        /// extra arguments to be passed for the test tool being used
364        /// (e.g. libtest, compiletest or rustdoc)
365        test_args: Vec<String>,
366        /// extra options to pass the compiler when running compiletest tests
367        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
368        compiletest_rustc_args: Vec<String>,
369        #[arg(long)]
370        /// do not run doc tests
371        no_doc: bool,
372        #[arg(long)]
373        /// only run doc tests
374        doc: bool,
375        #[arg(long)]
376        /// whether to automatically update stderr/stdout files
377        bless: bool,
378        #[arg(long)]
379        /// comma-separated list of other files types to check (accepts py, py:lint,
380        /// py:fmt, shell)
381        extra_checks: Option<String>,
382        #[arg(long)]
383        /// rerun tests even if the inputs are unchanged
384        force_rerun: bool,
385        #[arg(long)]
386        /// only run tests that result has been changed
387        only_modified: bool,
388        #[arg(long, value_name = "COMPARE MODE")]
389        /// mode describing what file the actual ui output will be compared to
390        compare_mode: Option<String>,
391        #[arg(long, value_name = "check | build | run")]
392        /// force {check,build,run}-pass tests to this mode.
393        pass: Option<String>,
394        #[arg(long, value_name = "auto | always | never")]
395        /// whether to execute run-* tests
396        run: Option<String>,
397        #[arg(long)]
398        /// enable this to generate a Rustfix coverage file, which is saved in
399        /// `/<build_base>/rustfix_missing_coverage.txt`
400        rustfix_coverage: bool,
401        #[arg(long)]
402        /// don't capture stdout/stderr of tests
403        no_capture: bool,
404    },
405    /// Build and run some test suites *in Miri*
406    Miri {
407        #[arg(long)]
408        /// run all tests regardless of failure
409        no_fail_fast: bool,
410        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
411        /// extra arguments to be passed for the test tool being used
412        /// (e.g. libtest, compiletest or rustdoc)
413        test_args: Vec<String>,
414        #[arg(long)]
415        /// do not run doc tests
416        no_doc: bool,
417        #[arg(long)]
418        /// only run doc tests
419        doc: bool,
420    },
421    /// Build and run some benchmarks
422    Bench {
423        #[arg(long, allow_hyphen_values(true))]
424        test_args: Vec<String>,
425    },
426    /// Clean out build directories
427    Clean {
428        #[arg(long)]
429        /// Clean the entire build directory (not used by default)
430        all: bool,
431        #[arg(long, value_name = "N")]
432        /// Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used.
433        stage: Option<u32>,
434    },
435    /// Build distribution artifacts
436    Dist,
437    /// Install distribution artifacts
438    Install,
439    #[command(aliases = ["r"], long_about = "\n
440    Arguments:
441        This subcommand accepts a number of paths to tools to build and run. For
442        example:
443            ./x.py run src/tools/bump-stage0
444        At least a tool needs to be called.")]
445    /// Run tools contained in this repository
446    Run {
447        /// arguments for the tool
448        #[arg(long, allow_hyphen_values(true))]
449        args: Vec<String>,
450    },
451    /// Set up the environment for development
452    #[command(long_about = format!(
453        "\n
454x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
455as well as setting up a git pre-push hook, VS Code config and toolchain link.
456Arguments:
457    This subcommand accepts a 'profile' to use for builds. For example:
458        ./x.py setup library
459    The profile is optional and you will be prompted interactively if it is not given.
460    The following profiles are available:
461{}
462    To only set up the git hook, editor config or toolchain link, you may use
463        ./x.py setup hook
464        ./x.py setup editor
465        ./x.py setup link", Profile::all_for_help("        ").trim_end()))]
466    Setup {
467        /// Either the profile for `bootstrap.toml` or another setup action.
468        /// May be omitted to set up interactively
469        #[arg(value_name = "<PROFILE>|hook|editor|link")]
470        profile: Option<PathBuf>,
471    },
472    /// Suggest a subset of tests to run, based on modified files
473    #[command(long_about = "\n")]
474    Suggest {
475        /// run suggested tests
476        #[arg(long)]
477        run: bool,
478    },
479    /// Vendor dependencies
480    Vendor {
481        /// Additional `Cargo.toml` to sync and vendor
482        #[arg(long)]
483        sync: Vec<PathBuf>,
484        /// Always include version in subdir name
485        #[arg(long)]
486        versioned_dirs: bool,
487    },
488    /// Perform profiling and benchmarking of the compiler using `rustc-perf`.
489    Perf(PerfArgs),
490}
491
492impl Subcommand {
493    pub fn kind(&self) -> Kind {
494        match self {
495            Subcommand::Bench { .. } => Kind::Bench,
496            Subcommand::Build => Kind::Build,
497            Subcommand::Check { .. } => Kind::Check,
498            Subcommand::Clippy { .. } => Kind::Clippy,
499            Subcommand::Doc { .. } => Kind::Doc,
500            Subcommand::Fix => Kind::Fix,
501            Subcommand::Format { .. } => Kind::Format,
502            Subcommand::Test { .. } => Kind::Test,
503            Subcommand::Miri { .. } => Kind::Miri,
504            Subcommand::Clean { .. } => Kind::Clean,
505            Subcommand::Dist => Kind::Dist,
506            Subcommand::Install => Kind::Install,
507            Subcommand::Run { .. } => Kind::Run,
508            Subcommand::Setup { .. } => Kind::Setup,
509            Subcommand::Suggest { .. } => Kind::Suggest,
510            Subcommand::Vendor { .. } => Kind::Vendor,
511            Subcommand::Perf { .. } => Kind::Perf,
512        }
513    }
514
515    pub fn compiletest_rustc_args(&self) -> Vec<&str> {
516        match *self {
517            Subcommand::Test { ref compiletest_rustc_args, .. } => {
518                compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
519            }
520            _ => vec![],
521        }
522    }
523
524    pub fn fail_fast(&self) -> bool {
525        match *self {
526            Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
527                !no_fail_fast
528            }
529            _ => false,
530        }
531    }
532
533    pub fn doc_tests(&self) -> DocTests {
534        match *self {
535            Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
536                if doc {
537                    DocTests::Only
538                } else if no_doc {
539                    DocTests::No
540                } else {
541                    DocTests::Yes
542                }
543            }
544            _ => DocTests::Yes,
545        }
546    }
547
548    pub fn bless(&self) -> bool {
549        match *self {
550            Subcommand::Test { bless, .. } => bless,
551            _ => false,
552        }
553    }
554
555    pub fn extra_checks(&self) -> Option<&str> {
556        match *self {
557            Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
558            _ => None,
559        }
560    }
561
562    pub fn only_modified(&self) -> bool {
563        match *self {
564            Subcommand::Test { only_modified, .. } => only_modified,
565            _ => false,
566        }
567    }
568
569    pub fn force_rerun(&self) -> bool {
570        match *self {
571            Subcommand::Test { force_rerun, .. } => force_rerun,
572            _ => false,
573        }
574    }
575
576    pub fn no_capture(&self) -> bool {
577        match *self {
578            Subcommand::Test { no_capture, .. } => no_capture,
579            _ => false,
580        }
581    }
582
583    pub fn rustfix_coverage(&self) -> bool {
584        match *self {
585            Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
586            _ => false,
587        }
588    }
589
590    pub fn compare_mode(&self) -> Option<&str> {
591        match *self {
592            Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
593            _ => None,
594        }
595    }
596
597    pub fn pass(&self) -> Option<&str> {
598        match *self {
599            Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
600            _ => None,
601        }
602    }
603
604    pub fn run(&self) -> Option<&str> {
605        match *self {
606            Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
607            _ => None,
608        }
609    }
610
611    pub fn open(&self) -> bool {
612        match *self {
613            Subcommand::Doc { open, .. } => open,
614            _ => false,
615        }
616    }
617
618    pub fn json(&self) -> bool {
619        match *self {
620            Subcommand::Doc { json, .. } => json,
621            _ => false,
622        }
623    }
624
625    pub fn vendor_versioned_dirs(&self) -> bool {
626        match *self {
627            Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
628            _ => false,
629        }
630    }
631
632    pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
633        match self {
634            Subcommand::Vendor { sync, .. } => sync.clone(),
635            _ => vec![],
636        }
637    }
638}
639
640/// Returns the shell completion for a given shell, if the result differs from the current
641/// content of `path`. If `path` does not exist, always returns `Some`.
642pub fn get_completion<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
643    let mut cmd = Flags::command();
644    let current = if !path.exists() {
645        String::new()
646    } else {
647        std::fs::read_to_string(path).unwrap_or_else(|_| {
648            eprintln!("couldn't read {}", path.display());
649            crate::exit!(1)
650        })
651    };
652    let mut buf = Vec::new();
653    let (bin_name, _) = path
654        .file_name()
655        .expect("path should be a regular file")
656        .to_str()
657        .expect("file name should be UTF-8")
658        .rsplit_once('.')
659        .expect("file name should have an extension");
660    clap_complete::generate(shell, &mut cmd, bin_name, &mut buf);
661    if buf == current.as_bytes() {
662        return None;
663    }
664    Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
665}