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 `config.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 config.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}
183
184impl Flags {
185    /// Check if `<cmd> -h -v` was passed.
186    /// If yes, print the available paths and return `true`.
187    pub fn try_parse_verbose_help(args: &[String]) -> bool {
188        // We need to check for `<cmd> -h -v`, in which case we list the paths
189        #[derive(Parser)]
190        #[command(disable_help_flag(true))]
191        struct HelpVerboseOnly {
192            #[arg(short, long)]
193            help: bool,
194            #[arg(global = true, short, long, action = clap::ArgAction::Count)]
195            pub verbose: u8,
196            #[arg(value_enum)]
197            cmd: Kind,
198        }
199        if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
200            HelpVerboseOnly::try_parse_from(normalize_args(args))
201        {
202            println!("NOTE: updating submodules before printing available paths");
203            let config = Config::parse(Self::parse(&[String::from("build")]));
204            let build = Build::new(config);
205            let paths = Builder::get_help(&build, subcommand);
206            if let Some(s) = paths {
207                println!("{s}");
208            } else {
209                panic!("No paths available for subcommand `{}`", subcommand.as_str());
210            }
211            true
212        } else {
213            false
214        }
215    }
216
217    #[cfg_attr(
218        feature = "tracing",
219        instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
220    )]
221    pub fn parse(args: &[String]) -> Self {
222        Flags::parse_from(normalize_args(args))
223    }
224}
225
226fn normalize_args(args: &[String]) -> Vec<String> {
227    let first = String::from("x.py");
228    let it = std::iter::once(first).chain(args.iter().cloned());
229    it.collect()
230}
231
232#[derive(Debug, Clone, Default, clap::Subcommand)]
233pub enum Subcommand {
234    #[command(aliases = ["b"], long_about = "\n
235    Arguments:
236        This subcommand accepts a number of paths to directories to the crates
237        and/or artifacts to compile. For example, for a quick build of a usable
238        compiler:
239            ./x.py build --stage 1 library/std
240        This will build a compiler and standard library from the local source code.
241        Once this is done, build/$ARCH/stage1 contains a usable compiler.
242        If no arguments are passed then the default artifacts for that stage are
243        compiled. For example:
244            ./x.py build --stage 0
245            ./x.py build ")]
246    /// Compile either the compiler or libraries
247    #[default]
248    Build,
249    #[command(aliases = ["c"], long_about = "\n
250    Arguments:
251        This subcommand accepts a number of paths to directories to the crates
252        and/or artifacts to compile. For example:
253            ./x.py check library/std
254        If no arguments are passed then many artifacts are checked.")]
255    /// Compile either the compiler or libraries, using cargo check
256    Check {
257        #[arg(long)]
258        /// Check all targets
259        all_targets: bool,
260    },
261    /// Run Clippy (uses rustup/cargo-installed clippy binary)
262    #[command(long_about = "\n
263    Arguments:
264        This subcommand accepts a number of paths to directories to the crates
265        and/or artifacts to run clippy against. For example:
266            ./x.py clippy library/core
267            ./x.py clippy library/core library/proc_macro")]
268    Clippy {
269        #[arg(long)]
270        fix: bool,
271        #[arg(long, requires = "fix")]
272        allow_dirty: bool,
273        #[arg(long, requires = "fix")]
274        allow_staged: bool,
275        /// clippy lints to allow
276        #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
277        allow: Vec<String>,
278        /// clippy lints to deny
279        #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
280        deny: Vec<String>,
281        /// clippy lints to warn on
282        #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
283        warn: Vec<String>,
284        /// clippy lints to forbid
285        #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
286        forbid: Vec<String>,
287    },
288    /// Run cargo fix
289    #[command(long_about = "\n
290    Arguments:
291        This subcommand accepts a number of paths to directories to the crates
292        and/or artifacts to run `cargo fix` against. For example:
293            ./x.py fix library/core
294            ./x.py fix library/core library/proc_macro")]
295    Fix,
296    #[command(
297        name = "fmt",
298        long_about = "\n
299    Arguments:
300        This subcommand optionally accepts a `--check` flag which succeeds if
301        formatting is correct and fails if it is not. For example:
302            ./x.py fmt
303            ./x.py fmt --check"
304    )]
305    /// Run rustfmt
306    Format {
307        /// check formatting instead of applying
308        #[arg(long)]
309        check: bool,
310
311        /// apply to all appropriate files, not just those that have been modified
312        #[arg(long)]
313        all: bool,
314    },
315    #[command(aliases = ["d"], long_about = "\n
316    Arguments:
317        This subcommand accepts a number of paths to directories of documentation
318        to build. For example:
319            ./x.py doc src/doc/book
320            ./x.py doc src/doc/nomicon
321            ./x.py doc src/doc/book library/std
322            ./x.py doc library/std --json
323            ./x.py doc library/std --open
324        If no arguments are passed then everything is documented:
325            ./x.py doc
326            ./x.py doc --stage 1")]
327    /// Build documentation
328    Doc {
329        #[arg(long)]
330        /// open the docs in a browser
331        open: bool,
332        #[arg(long)]
333        /// render the documentation in JSON format in addition to the usual HTML format
334        json: bool,
335    },
336    #[command(aliases = ["t"], long_about = "\n
337    Arguments:
338        This subcommand accepts a number of paths to test directories that
339        should be compiled and run. For example:
340            ./x.py test tests/ui
341            ./x.py test library/std --test-args hash_map
342            ./x.py test library/std --stage 0 --no-doc
343            ./x.py test tests/ui --bless
344            ./x.py test tests/ui --compare-mode next-solver
345        Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
346        just like `build library/std --stage N` it tests the compiler produced by the previous
347        stage.
348        Execute tool tests with a tool name argument:
349            ./x.py test tidy
350        If no arguments are passed then the complete artifacts for that stage are
351        compiled and tested.
352            ./x.py test
353            ./x.py test --stage 1")]
354    /// Build and run some test suites
355    Test {
356        #[arg(long)]
357        /// run all tests regardless of failure
358        no_fail_fast: bool,
359        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
360        /// extra arguments to be passed for the test tool being used
361        /// (e.g. libtest, compiletest or rustdoc)
362        test_args: Vec<String>,
363        /// extra options to pass the compiler when running compiletest tests
364        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
365        compiletest_rustc_args: Vec<String>,
366        #[arg(long)]
367        /// do not run doc tests
368        no_doc: bool,
369        #[arg(long)]
370        /// only run doc tests
371        doc: bool,
372        #[arg(long)]
373        /// whether to automatically update stderr/stdout files
374        bless: bool,
375        #[arg(long)]
376        /// comma-separated list of other files types to check (accepts py, py:lint,
377        /// py:fmt, shell)
378        extra_checks: Option<String>,
379        #[arg(long)]
380        /// rerun tests even if the inputs are unchanged
381        force_rerun: bool,
382        #[arg(long)]
383        /// only run tests that result has been changed
384        only_modified: bool,
385        #[arg(long, value_name = "COMPARE MODE")]
386        /// mode describing what file the actual ui output will be compared to
387        compare_mode: Option<String>,
388        #[arg(long, value_name = "check | build | run")]
389        /// force {check,build,run}-pass tests to this mode.
390        pass: Option<String>,
391        #[arg(long, value_name = "auto | always | never")]
392        /// whether to execute run-* tests
393        run: Option<String>,
394        #[arg(long)]
395        /// enable this to generate a Rustfix coverage file, which is saved in
396        /// `/<build_base>/rustfix_missing_coverage.txt`
397        rustfix_coverage: bool,
398        #[arg(long)]
399        /// don't capture stdout/stderr of tests
400        no_capture: bool,
401    },
402    /// Build and run some test suites *in Miri*
403    Miri {
404        #[arg(long)]
405        /// run all tests regardless of failure
406        no_fail_fast: bool,
407        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
408        /// extra arguments to be passed for the test tool being used
409        /// (e.g. libtest, compiletest or rustdoc)
410        test_args: Vec<String>,
411        #[arg(long)]
412        /// do not run doc tests
413        no_doc: bool,
414        #[arg(long)]
415        /// only run doc tests
416        doc: bool,
417    },
418    /// Build and run some benchmarks
419    Bench {
420        #[arg(long, allow_hyphen_values(true))]
421        test_args: Vec<String>,
422    },
423    /// Clean out build directories
424    Clean {
425        #[arg(long)]
426        /// Clean the entire build directory (not used by default)
427        all: bool,
428        #[arg(long, value_name = "N")]
429        /// Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used.
430        stage: Option<u32>,
431    },
432    /// Build distribution artifacts
433    Dist,
434    /// Install distribution artifacts
435    Install,
436    #[command(aliases = ["r"], long_about = "\n
437    Arguments:
438        This subcommand accepts a number of paths to tools to build and run. For
439        example:
440            ./x.py run src/tools/bump-stage0
441        At least a tool needs to be called.")]
442    /// Run tools contained in this repository
443    Run {
444        /// arguments for the tool
445        #[arg(long, allow_hyphen_values(true))]
446        args: Vec<String>,
447    },
448    /// Set up the environment for development
449    #[command(long_about = format!(
450        "\n
451x.py setup creates a `config.toml` which changes the defaults for x.py itself,
452as well as setting up a git pre-push hook, VS Code config and toolchain link.
453Arguments:
454    This subcommand accepts a 'profile' to use for builds. For example:
455        ./x.py setup library
456    The profile is optional and you will be prompted interactively if it is not given.
457    The following profiles are available:
458{}
459    To only set up the git hook, editor config or toolchain link, you may use
460        ./x.py setup hook
461        ./x.py setup editor
462        ./x.py setup link", Profile::all_for_help("        ").trim_end()))]
463    Setup {
464        /// Either the profile for `config.toml` or another setup action.
465        /// May be omitted to set up interactively
466        #[arg(value_name = "<PROFILE>|hook|editor|link")]
467        profile: Option<PathBuf>,
468    },
469    /// Suggest a subset of tests to run, based on modified files
470    #[command(long_about = "\n")]
471    Suggest {
472        /// run suggested tests
473        #[arg(long)]
474        run: bool,
475    },
476    /// Vendor dependencies
477    Vendor {
478        /// Additional `Cargo.toml` to sync and vendor
479        #[arg(long)]
480        sync: Vec<PathBuf>,
481        /// Always include version in subdir name
482        #[arg(long)]
483        versioned_dirs: bool,
484    },
485    /// Perform profiling and benchmarking of the compiler using `rustc-perf`.
486    Perf(PerfArgs),
487}
488
489impl Subcommand {
490    pub fn kind(&self) -> Kind {
491        match self {
492            Subcommand::Bench { .. } => Kind::Bench,
493            Subcommand::Build { .. } => Kind::Build,
494            Subcommand::Check { .. } => Kind::Check,
495            Subcommand::Clippy { .. } => Kind::Clippy,
496            Subcommand::Doc { .. } => Kind::Doc,
497            Subcommand::Fix { .. } => Kind::Fix,
498            Subcommand::Format { .. } => Kind::Format,
499            Subcommand::Test { .. } => Kind::Test,
500            Subcommand::Miri { .. } => Kind::Miri,
501            Subcommand::Clean { .. } => Kind::Clean,
502            Subcommand::Dist { .. } => Kind::Dist,
503            Subcommand::Install { .. } => Kind::Install,
504            Subcommand::Run { .. } => Kind::Run,
505            Subcommand::Setup { .. } => Kind::Setup,
506            Subcommand::Suggest { .. } => Kind::Suggest,
507            Subcommand::Vendor { .. } => Kind::Vendor,
508            Subcommand::Perf { .. } => Kind::Perf,
509        }
510    }
511
512    pub fn compiletest_rustc_args(&self) -> Vec<&str> {
513        match *self {
514            Subcommand::Test { ref compiletest_rustc_args, .. } => {
515                compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
516            }
517            _ => vec![],
518        }
519    }
520
521    pub fn fail_fast(&self) -> bool {
522        match *self {
523            Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
524                !no_fail_fast
525            }
526            _ => false,
527        }
528    }
529
530    pub fn doc_tests(&self) -> DocTests {
531        match *self {
532            Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
533                if doc {
534                    DocTests::Only
535                } else if no_doc {
536                    DocTests::No
537                } else {
538                    DocTests::Yes
539                }
540            }
541            _ => DocTests::Yes,
542        }
543    }
544
545    pub fn bless(&self) -> bool {
546        match *self {
547            Subcommand::Test { bless, .. } => bless,
548            _ => false,
549        }
550    }
551
552    pub fn extra_checks(&self) -> Option<&str> {
553        match *self {
554            Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
555            _ => None,
556        }
557    }
558
559    pub fn only_modified(&self) -> bool {
560        match *self {
561            Subcommand::Test { only_modified, .. } => only_modified,
562            _ => false,
563        }
564    }
565
566    pub fn force_rerun(&self) -> bool {
567        match *self {
568            Subcommand::Test { force_rerun, .. } => force_rerun,
569            _ => false,
570        }
571    }
572
573    pub fn no_capture(&self) -> bool {
574        match *self {
575            Subcommand::Test { no_capture, .. } => no_capture,
576            _ => false,
577        }
578    }
579
580    pub fn rustfix_coverage(&self) -> bool {
581        match *self {
582            Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
583            _ => false,
584        }
585    }
586
587    pub fn compare_mode(&self) -> Option<&str> {
588        match *self {
589            Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
590            _ => None,
591        }
592    }
593
594    pub fn pass(&self) -> Option<&str> {
595        match *self {
596            Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
597            _ => None,
598        }
599    }
600
601    pub fn run(&self) -> Option<&str> {
602        match *self {
603            Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
604            _ => None,
605        }
606    }
607
608    pub fn open(&self) -> bool {
609        match *self {
610            Subcommand::Doc { open, .. } => open,
611            _ => false,
612        }
613    }
614
615    pub fn json(&self) -> bool {
616        match *self {
617            Subcommand::Doc { json, .. } => json,
618            _ => false,
619        }
620    }
621
622    pub fn vendor_versioned_dirs(&self) -> bool {
623        match *self {
624            Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
625            _ => false,
626        }
627    }
628
629    pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
630        match self {
631            Subcommand::Vendor { sync, .. } => sync.clone(),
632            _ => vec![],
633        }
634    }
635}
636
637/// Returns the shell completion for a given shell, if the result differs from the current
638/// content of `path`. If `path` does not exist, always returns `Some`.
639pub fn get_completion<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
640    let mut cmd = Flags::command();
641    let current = if !path.exists() {
642        String::new()
643    } else {
644        std::fs::read_to_string(path).unwrap_or_else(|_| {
645            eprintln!("couldn't read {}", path.display());
646            crate::exit!(1)
647        })
648    };
649    let mut buf = Vec::new();
650    let (bin_name, _) = path
651        .file_name()
652        .expect("path should be a regular file")
653        .to_str()
654        .expect("file name should be UTF-8")
655        .rsplit_once('.')
656        .expect("file name should have an extension");
657    clap_complete::generate(shell, &mut cmd, bin_name, &mut buf);
658    if buf == current.as_bytes() {
659        return None;
660    }
661    Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
662}