1use std::path::{Path, PathBuf};
7
8use clap::{CommandFactory, Parser, ValueEnum};
9use clap_complete::Generator;
10#[cfg(feature = "tracing")]
11use tracing::instrument;
12
13use crate::core::build_steps::perf::PerfArgs;
14use crate::core::build_steps::setup::Profile;
15use crate::core::builder::{Builder, Kind};
16use crate::core::config::Config;
17use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
18use crate::{Build, DocTests};
19
20#[derive(Copy, Clone, Default, Debug, ValueEnum)]
21pub enum Color {
22    Always,
23    Never,
24    #[default]
25    Auto,
26}
27
28#[derive(Copy, Clone, Default, Debug, ValueEnum)]
30pub enum Warnings {
31    Deny,
32    Warn,
33    #[default]
34    Default,
35}
36
37#[derive(Debug, Parser)]
39#[command(
40    override_usage = "x.py <subcommand> [options] [<paths>...]",
41    disable_help_subcommand(true),
42    about = "",
43    next_line_help(false)
44)]
45pub struct Flags {
46    #[command(subcommand)]
47    pub cmd: Subcommand,
48
49    #[arg(global = true, short, long, action = clap::ArgAction::Count)]
50    pub verbose: u8, #[arg(global = true, short, long)]
53    pub incremental: bool,
55    #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
56    pub config: Option<PathBuf>,
58    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
59    pub build_dir: Option<PathBuf>,
61
62    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
63    pub build: Option<String>,
65
66    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
67    pub host: Option<TargetSelectionList>,
69
70    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
71    pub target: Option<TargetSelectionList>,
73
74    #[arg(global = true, long, value_name = "PATH")]
75    pub exclude: Vec<PathBuf>, #[arg(global = true, long, value_name = "PATH")]
78    pub skip: Vec<PathBuf>,
80    #[arg(global = true, long)]
81    pub include_default_paths: bool,
83
84    #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
86    pub rustc_error_format: Option<String>,
87
88    #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
89    pub on_fail: Option<String>,
91    #[arg(global = true, long)]
92    pub dry_run: bool,
94    #[arg(global = true, long)]
96    pub dump_bootstrap_shims: bool,
97    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
98    pub stage: Option<u32>,
101
102    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
103    pub keep_stage: Vec<u32>,
106    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
107    pub keep_stage_std: Vec<u32>,
110    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
111    pub src: Option<PathBuf>,
113
114    #[arg(
115        global = true,
116        short,
117        long,
118        value_hint = clap::ValueHint::Other,
119        value_name = "JOBS"
120    )]
121    pub jobs: Option<u32>,
123    #[arg(global = true, long)]
126    #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
127    pub warnings: Warnings,
131
132    #[arg(global = true, long)]
133    pub json_output: bool,
135    #[arg(global = true, long)]
136    pub compile_time_deps: bool,
138
139    #[arg(global = true, long, value_name = "STYLE")]
140    #[arg(value_enum, default_value_t = Color::Auto)]
141    pub color: Color,
143
144    #[arg(global = true, long)]
145    pub bypass_bootstrap_lock: bool,
150
151    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
153    pub rust_profile_generate: Option<String>,
154    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
156    pub rust_profile_use: Option<String>,
157    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
159    pub llvm_profile_use: Option<String>,
160    #[arg(global = true, long)]
166    pub llvm_profile_generate: bool,
167    #[arg(global = true, long)]
169    pub enable_bolt_settings: bool,
170    #[arg(global = true, long)]
172    pub skip_stage0_validation: bool,
173    #[arg(global = true, long)]
175    pub reproducible_artifact: Vec<String>,
176    #[arg(global = true)]
177    pub paths: Vec<PathBuf>,
179    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
181    pub set: Vec<String>,
182    #[arg(global = true, last(true), value_name = "ARGS")]
184    pub free_args: Vec<String>,
185    #[arg(global = true, long, value_name = "bool")]
187    pub ci: Option<bool>,
188    #[arg(global = true, long)]
192    pub skip_std_check_if_no_download_rustc: bool,
193}
194
195impl Flags {
196    pub fn try_parse_verbose_help(args: &[String]) -> bool {
199        #[derive(Parser)]
201        #[command(disable_help_flag(true))]
202        struct HelpVerboseOnly {
203            #[arg(short, long)]
204            help: bool,
205            #[arg(global = true, short, long, action = clap::ArgAction::Count)]
206            pub verbose: u8,
207            #[arg(value_enum)]
208            cmd: Kind,
209        }
210        if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
211            HelpVerboseOnly::try_parse_from(normalize_args(args))
212        {
213            println!("NOTE: updating submodules before printing available paths");
214            let flags = Self::parse(&[String::from("build")]);
215            let config = Config::parse(flags);
216            let build = Build::new(config);
217            let paths = Builder::get_help(&build, subcommand);
218            if let Some(s) = paths {
219                println!("{s}");
220            } else {
221                panic!("No paths available for subcommand `{}`", subcommand.as_str());
222            }
223            true
224        } else {
225            false
226        }
227    }
228
229    #[cfg_attr(
230        feature = "tracing",
231        instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
232    )]
233    pub fn parse(args: &[String]) -> Self {
234        Flags::parse_from(normalize_args(args))
235    }
236}
237
238fn normalize_args(args: &[String]) -> Vec<String> {
239    let first = String::from("x.py");
240    let it = std::iter::once(first).chain(args.iter().cloned());
241    it.collect()
242}
243
244#[derive(Debug, Clone, Default, clap::Subcommand)]
245pub enum Subcommand {
246    #[command(aliases = ["b"], long_about = "\n
247    Arguments:
248        This subcommand accepts a number of paths to directories to the crates
249        and/or artifacts to compile. For example, for a quick build of a usable
250        compiler:
251            ./x.py build --stage 1 library/std
252        This will build a compiler and standard library from the local source code.
253        Once this is done, build/$ARCH/stage1 contains a usable compiler.
254        If no arguments are passed then the default artifacts for that stage are
255        compiled. For example:
256            ./x.py build --stage 0
257            ./x.py build ")]
258    #[default]
260    Build,
261    #[command(aliases = ["c"], long_about = "\n
262    Arguments:
263        This subcommand accepts a number of paths to directories to the crates
264        and/or artifacts to compile. For example:
265            ./x.py check library/std
266        If no arguments are passed then many artifacts are checked.")]
267    Check {
269        #[arg(long)]
270        all_targets: bool,
272    },
273    #[command(long_about = "\n
275    Arguments:
276        This subcommand accepts a number of paths to directories to the crates
277        and/or artifacts to run clippy against. For example:
278            ./x.py clippy library/core
279            ./x.py clippy library/core library/proc_macro")]
280    Clippy {
281        #[arg(long)]
282        fix: bool,
283        #[arg(long, requires = "fix")]
284        allow_dirty: bool,
285        #[arg(long, requires = "fix")]
286        allow_staged: bool,
287        #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
289        allow: Vec<String>,
290        #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
292        deny: Vec<String>,
293        #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
295        warn: Vec<String>,
296        #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
298        forbid: Vec<String>,
299    },
300    #[command(long_about = "\n
302    Arguments:
303        This subcommand accepts a number of paths to directories to the crates
304        and/or artifacts to run `cargo fix` against. For example:
305            ./x.py fix library/core
306            ./x.py fix library/core library/proc_macro")]
307    Fix,
308    #[command(
309        name = "fmt",
310        long_about = "\n
311    Arguments:
312        This subcommand optionally accepts a `--check` flag which succeeds if
313        formatting is correct and fails if it is not. For example:
314            ./x.py fmt
315            ./x.py fmt --check"
316    )]
317    Format {
319        #[arg(long)]
321        check: bool,
322
323        #[arg(long)]
325        all: bool,
326    },
327    #[command(aliases = ["d"], long_about = "\n
328    Arguments:
329        This subcommand accepts a number of paths to directories of documentation
330        to build. For example:
331            ./x.py doc src/doc/book
332            ./x.py doc src/doc/nomicon
333            ./x.py doc src/doc/book library/std
334            ./x.py doc library/std --json
335            ./x.py doc library/std --open
336        If no arguments are passed then everything is documented:
337            ./x.py doc
338            ./x.py doc --stage 1")]
339    Doc {
341        #[arg(long)]
342        open: bool,
344        #[arg(long)]
345        json: bool,
347    },
348    #[command(aliases = ["t"], long_about = "\n
349    Arguments:
350        This subcommand accepts a number of paths to test directories that
351        should be compiled and run. For example:
352            ./x.py test tests/ui
353            ./x.py test library/std --test-args hash_map
354            ./x.py test library/std --stage 0 --no-doc
355            ./x.py test tests/ui --bless
356            ./x.py test tests/ui --compare-mode next-solver
357        Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
358        just like `build library/std --stage N` it tests the compiler produced by the previous
359        stage.
360        Execute tool tests with a tool name argument:
361            ./x.py test tidy
362        If no arguments are passed then the complete artifacts for that stage are
363        compiled and tested.
364            ./x.py test
365            ./x.py test --stage 1")]
366    Test {
368        #[arg(long)]
369        no_fail_fast: bool,
371        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
372        test_args: Vec<String>,
375        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
377        compiletest_rustc_args: Vec<String>,
378        #[arg(long)]
379        no_doc: bool,
381        #[arg(long)]
382        doc: bool,
384        #[arg(long)]
385        bless: bool,
387        #[arg(long)]
388        extra_checks: Option<String>,
394        #[arg(long)]
395        force_rerun: bool,
397        #[arg(long)]
398        only_modified: bool,
400        #[arg(long, value_name = "COMPARE MODE")]
401        compare_mode: Option<String>,
403        #[arg(long, value_name = "check | build | run")]
404        pass: Option<String>,
406        #[arg(long, value_name = "auto | always | never")]
407        run: Option<String>,
409        #[arg(long)]
410        rustfix_coverage: bool,
413        #[arg(long)]
414        no_capture: bool,
416    },
417    Miri {
419        #[arg(long)]
420        no_fail_fast: bool,
422        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
423        test_args: Vec<String>,
426        #[arg(long)]
427        no_doc: bool,
429        #[arg(long)]
430        doc: bool,
432    },
433    Bench {
435        #[arg(long, allow_hyphen_values(true))]
436        test_args: Vec<String>,
437    },
438    Clean {
440        #[arg(long)]
441        all: bool,
443        #[arg(long, value_name = "N")]
444        stage: Option<u32>,
446    },
447    Dist,
449    Install,
451    #[command(aliases = ["r"], long_about = "\n
452    Arguments:
453        This subcommand accepts a number of paths to tools to build and run. For
454        example:
455            ./x.py run src/tools/bump-stage0
456        At least a tool needs to be called.")]
457    Run {
459        #[arg(long, allow_hyphen_values(true))]
461        args: Vec<String>,
462    },
463    #[command(long_about = format!(
465        "\n
466x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
467as well as setting up a git pre-push hook, VS Code config and toolchain link.
468Arguments:
469    This subcommand accepts a 'profile' to use for builds. For example:
470        ./x.py setup library
471    The profile is optional and you will be prompted interactively if it is not given.
472    The following profiles are available:
473{}
474    To only set up the git hook, editor config or toolchain link, you may use
475        ./x.py setup hook
476        ./x.py setup editor
477        ./x.py setup link", Profile::all_for_help("        ").trim_end()))]
478    Setup {
479        #[arg(value_name = "<PROFILE>|hook|editor|link")]
482        profile: Option<PathBuf>,
483    },
484    Vendor {
486        #[arg(long)]
488        sync: Vec<PathBuf>,
489        #[arg(long)]
491        versioned_dirs: bool,
492    },
493    Perf(PerfArgs),
495}
496
497impl Subcommand {
498    pub fn kind(&self) -> Kind {
499        match self {
500            Subcommand::Bench { .. } => Kind::Bench,
501            Subcommand::Build => Kind::Build,
502            Subcommand::Check { .. } => Kind::Check,
503            Subcommand::Clippy { .. } => Kind::Clippy,
504            Subcommand::Doc { .. } => Kind::Doc,
505            Subcommand::Fix => Kind::Fix,
506            Subcommand::Format { .. } => Kind::Format,
507            Subcommand::Test { .. } => Kind::Test,
508            Subcommand::Miri { .. } => Kind::Miri,
509            Subcommand::Clean { .. } => Kind::Clean,
510            Subcommand::Dist => Kind::Dist,
511            Subcommand::Install => Kind::Install,
512            Subcommand::Run { .. } => Kind::Run,
513            Subcommand::Setup { .. } => Kind::Setup,
514            Subcommand::Vendor { .. } => Kind::Vendor,
515            Subcommand::Perf { .. } => Kind::Perf,
516        }
517    }
518
519    pub fn compiletest_rustc_args(&self) -> Vec<&str> {
520        match *self {
521            Subcommand::Test { ref compiletest_rustc_args, .. } => {
522                compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
523            }
524            _ => vec![],
525        }
526    }
527
528    pub fn fail_fast(&self) -> bool {
529        match *self {
530            Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
531                !no_fail_fast
532            }
533            _ => false,
534        }
535    }
536
537    pub fn doc_tests(&self) -> DocTests {
538        match *self {
539            Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
540                if doc {
541                    DocTests::Only
542                } else if no_doc {
543                    DocTests::No
544                } else {
545                    DocTests::Yes
546                }
547            }
548            _ => DocTests::Yes,
549        }
550    }
551
552    pub fn bless(&self) -> bool {
553        match *self {
554            Subcommand::Test { bless, .. } => bless,
555            _ => false,
556        }
557    }
558
559    pub fn extra_checks(&self) -> Option<&str> {
560        match *self {
561            Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
562            _ => None,
563        }
564    }
565
566    pub fn only_modified(&self) -> bool {
567        match *self {
568            Subcommand::Test { only_modified, .. } => only_modified,
569            _ => false,
570        }
571    }
572
573    pub fn force_rerun(&self) -> bool {
574        match *self {
575            Subcommand::Test { force_rerun, .. } => force_rerun,
576            _ => false,
577        }
578    }
579
580    pub fn no_capture(&self) -> bool {
581        match *self {
582            Subcommand::Test { no_capture, .. } => no_capture,
583            _ => false,
584        }
585    }
586
587    pub fn rustfix_coverage(&self) -> bool {
588        match *self {
589            Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
590            _ => false,
591        }
592    }
593
594    pub fn compare_mode(&self) -> Option<&str> {
595        match *self {
596            Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
597            _ => None,
598        }
599    }
600
601    pub fn pass(&self) -> Option<&str> {
602        match *self {
603            Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
604            _ => None,
605        }
606    }
607
608    pub fn run(&self) -> Option<&str> {
609        match *self {
610            Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
611            _ => None,
612        }
613    }
614
615    pub fn open(&self) -> bool {
616        match *self {
617            Subcommand::Doc { open, .. } => open,
618            _ => false,
619        }
620    }
621
622    pub fn json(&self) -> bool {
623        match *self {
624            Subcommand::Doc { json, .. } => json,
625            _ => false,
626        }
627    }
628
629    pub fn vendor_versioned_dirs(&self) -> bool {
630        match *self {
631            Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
632            _ => false,
633        }
634    }
635
636    pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
637        match self {
638            Subcommand::Vendor { sync, .. } => sync.clone(),
639            _ => vec![],
640        }
641    }
642}
643
644pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option<String> {
647    let mut cmd = Flags::command();
648    let current = if !path.exists() {
649        String::new()
650    } else {
651        std::fs::read_to_string(path).unwrap_or_else(|_| {
652            eprintln!("couldn't read {}", path.display());
653            crate::exit!(1)
654        })
655    };
656    let mut buf = Vec::new();
657    let (bin_name, _) = path
658        .file_name()
659        .expect("path should be a regular file")
660        .to_str()
661        .expect("file name should be UTF-8")
662        .rsplit_once('.')
663        .expect("file name should have an extension");
664
665    cmd.set_bin_name(bin_name);
668    cmd.build();
669    shell.generate(&cmd, &mut buf);
670    if buf == current.as_bytes() {
671        return None;
672    }
673    Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
674}