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::Build;
14use crate::core::backend::CodegenBackendKind;
15use crate::core::build_steps::perf::PerfArgs;
16use crate::core::build_steps::setup::Profile;
17use crate::core::build_steps::test::TestTarget;
18use crate::core::builder::{Builder, Kind};
19use crate::core::config::Config;
20use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
21use crate::utils::helpers;
22
23#[derive(Copy, Clone, Default, Debug, ValueEnum)]
24pub enum Color {
25 Always,
26 Never,
27 #[default]
28 Auto,
29}
30
31#[derive(Copy, Clone, Default, Debug, ValueEnum)]
33pub enum Warnings {
34 Deny,
35 Warn,
36 #[default]
37 Default,
38}
39
40#[derive(Debug, Parser)]
42#[command(
43 override_usage = "x.py <subcommand> [options] [<paths>...]",
44 disable_help_subcommand(true),
45 about = "",
46 next_line_help(false)
47)]
48pub struct Flags {
49 #[command(subcommand)]
50 pub cmd: Subcommand,
51
52 #[arg(global = true, short, long, action = clap::ArgAction::Count, conflicts_with = "quiet")]
53 pub verbose: u8, #[arg(global = true, short, long, conflicts_with = "verbose")]
56 pub quiet: bool,
58 #[arg(global = true, short, long)]
59 pub incremental: bool,
61 #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
62 pub config: Option<PathBuf>,
64 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
65 pub build_dir: Option<PathBuf>,
67
68 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
69 pub build: Option<String>,
71
72 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
73 pub host: Option<TargetSelectionList>,
75
76 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
77 pub target: Option<TargetSelectionList>,
79
80 #[arg(global = true, long, value_name = "PATH")]
81 pub exclude: Vec<PathBuf>, #[arg(global = true, long, value_name = "PATH")]
84 pub skip: Vec<PathBuf>,
86 #[arg(global = true, long)]
87 pub include_default_paths: bool,
89
90 #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
92 pub rustc_error_format: Option<String>,
93
94 #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
95 pub on_fail: Option<String>,
97 #[arg(global = true, long)]
98 pub dry_run: bool,
100 #[arg(global = true, long)]
102 pub dump_bootstrap_shims: bool,
103 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
104 pub stage: Option<u32>,
107
108 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
109 pub keep_stage: Vec<u32>,
112 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
113 pub keep_stage_std: Vec<u32>,
116 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
117 pub src: Option<PathBuf>,
119
120 #[arg(
121 global = true,
122 short,
123 long,
124 value_hint = clap::ValueHint::Other,
125 value_name = "JOBS"
126 )]
127 pub jobs: Option<u32>,
129 #[arg(global = true, long)]
132 #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
133 pub warnings: Warnings,
137
138 #[arg(global = true, long)]
139 pub json_output: bool,
141 #[arg(global = true, long)]
142 pub compile_time_deps: bool,
144
145 #[arg(global = true, long, value_name = "STYLE")]
146 #[arg(value_enum, default_value_t = Color::Auto)]
147 pub color: Color,
149
150 #[arg(global = true, long)]
151 pub bypass_bootstrap_lock: bool,
156
157 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
159 pub rust_profile_generate: Option<PathBuf>,
161 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
164 pub rust_profile_use: Option<PathBuf>,
165 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
168 pub llvm_profile_use: Option<PathBuf>,
169 #[arg(global = true, long)]
176 pub llvm_profile_generate: bool,
177 #[arg(global = true, long)]
179 pub enable_bolt_settings: bool,
180 #[arg(global = true, long)]
182 pub skip_stage0_validation: bool,
183 #[arg(global = true, long)]
185 pub reproducible_artifact: Vec<String>,
186 #[arg(global = true)]
187 pub paths: Vec<PathBuf>,
189 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
191 pub set: Vec<String>,
192 #[arg(global = true, last(true), value_name = "ARGS")]
194 pub free_args: Vec<String>,
195 #[arg(global = true, long, value_name = "bool")]
197 pub ci: Option<bool>,
198 #[arg(global = true, long)]
202 pub skip_std_check_if_no_download_rustc: bool,
203}
204
205impl Flags {
206 pub fn try_parse_verbose_help(args: &[String]) -> bool {
209 #[derive(Parser)]
211 #[command(disable_help_flag(true))]
212 struct HelpVerboseOnly {
213 #[arg(short, long)]
214 help: bool,
215 #[arg(global = true, short, long, action = clap::ArgAction::Count)]
216 pub verbose: u8,
217 #[arg(value_enum)]
218 cmd: Kind,
219 }
220 if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
221 HelpVerboseOnly::try_parse_from(normalize_args(args))
222 {
223 println!("NOTE: updating submodules before printing available paths");
224 let flags = Self::parse(&[String::from("build")]);
225 let config = Config::parse(flags);
226 let build = Build::new(config);
227 let paths = Builder::get_help(&build, subcommand);
228 if let Some(s) = paths {
229 println!("{s}");
230 } else {
231 panic!("No paths available for subcommand `{}`", subcommand.as_str());
232 }
233 true
234 } else {
235 false
236 }
237 }
238
239 #[cfg_attr(
240 feature = "tracing",
241 instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
242 )]
243 pub fn parse(args: &[String]) -> Self {
244 Flags::parse_from(normalize_args(args))
245 }
246}
247
248fn normalize_args(args: &[String]) -> Vec<String> {
249 let first = String::from("x.py");
250 let it = std::iter::once(first).chain(args.iter().cloned());
251 it.collect()
252}
253
254#[derive(Debug, Clone, clap::Subcommand)]
255pub enum Subcommand {
256 #[command(visible_aliases = ["b"], long_about = "\n
257 Arguments:
258 This subcommand accepts a number of paths to directories to the crates
259 and/or artifacts to compile. For example, for a quick build of a usable
260 compiler:
261 ./x.py build --stage 1 library/std
262 This will build a compiler and standard library from the local source code.
263 Once this is done, build/$ARCH/stage1 contains a usable compiler.
264 If no arguments are passed then the default artifacts for that stage are
265 compiled. For example:
266 ./x.py build --stage 0
267 ./x.py build ")]
268 Build {
270 #[arg(long)]
271 timings: bool,
273 },
274 #[command(visible_aliases = ["c"], long_about = "\n
275 Arguments:
276 This subcommand accepts a number of paths to directories to the crates
277 and/or artifacts to compile. For example:
278 ./x.py check library/std
279 If no arguments are passed then many artifacts are checked.")]
280 Check {
282 #[arg(long)]
283 all_targets: bool,
285 #[arg(long)]
286 timings: bool,
288 },
289 #[command(long_about = "\n
291 Arguments:
292 This subcommand accepts a number of paths to directories to the crates
293 and/or artifacts to run clippy against. For example:
294 ./x.py clippy library/core
295 ./x.py clippy library/core library/proc_macro")]
296 Clippy {
297 #[arg(long)]
298 fix: bool,
299 #[arg(long, requires = "fix")]
300 allow_dirty: bool,
301 #[arg(long, requires = "fix")]
302 allow_staged: bool,
303 #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
305 allow: Vec<String>,
306 #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
308 deny: Vec<String>,
309 #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
311 warn: Vec<String>,
312 #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
314 forbid: Vec<String>,
315 },
316 #[command(long_about = "\n
318 Arguments:
319 This subcommand accepts a number of paths to directories to the crates
320 and/or artifacts to run `cargo fix` against. For example:
321 ./x.py fix library/core
322 ./x.py fix library/core library/proc_macro")]
323 Fix,
324 #[command(
325 name = "fmt",
326 long_about = "\n
327 Arguments:
328 This subcommand optionally accepts a `--check` flag which succeeds if
329 formatting is correct and fails if it is not. For example:
330 ./x.py fmt
331 ./x.py fmt --check"
332 )]
333 Format {
335 #[arg(long)]
337 check: bool,
338
339 #[arg(long)]
341 all: bool,
342 },
343 #[command(visible_aliases = ["d"], long_about = "\n
344 Arguments:
345 This subcommand accepts a number of paths to directories of documentation
346 to build. For example:
347 ./x.py doc src/doc/book
348 ./x.py doc src/doc/nomicon
349 ./x.py doc src/doc/book library/std
350 ./x.py doc library/std --json
351 ./x.py doc library/std --open
352 If no arguments are passed then everything is documented:
353 ./x.py doc
354 ./x.py doc --stage 1")]
355 Doc {
357 #[arg(long)]
358 open: bool,
360 #[arg(long)]
361 json: bool,
363 },
364 #[command(visible_aliases = ["t"], long_about = "\n
365 Arguments:
366 This subcommand accepts a number of paths to test directories that
367 should be compiled and run. For example:
368 ./x.py test tests/ui
369 ./x.py test library/std --test-args hash_map
370 ./x.py test library/std --stage 0 --all-targets
371 ./x.py test tests/ui --bless
372 ./x.py test tests/ui --compare-mode next-solver
373 Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
374 just like `build library/std --stage N` it tests the compiler produced by the previous
375 stage.
376 Execute tool tests with a tool name argument:
377 ./x.py test tidy
378 If no arguments are passed then the complete artifacts for that stage are
379 compiled and tested.
380 ./x.py test
381 ./x.py test --stage 1")]
382 Test {
384 #[arg(long)]
385 no_fail_fast: bool,
387 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
388 test_args: Vec<String>,
391 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
393 compiletest_rustc_args: Vec<String>,
394 #[arg(long)]
395 all_targets: bool,
397 #[arg(long)]
398 doc: bool,
400 #[arg(long)]
402 tests: bool,
403 #[arg(long)]
404 bless: bool,
406 #[arg(long)]
407 extra_checks: Option<String>,
413 #[arg(long)]
414 force_rerun: bool,
416 #[arg(long)]
417 only_modified: bool,
419 #[arg(long, value_name = "COMPARE MODE")]
420 compare_mode: Option<String>,
422 #[arg(long, value_name = "check | build | run")]
423 pass: Option<String>,
425 #[arg(long, value_name = "auto | always | never")]
426 run: Option<String>,
428 #[arg(long)]
429 rustfix_coverage: bool,
432 #[arg(long)]
433 no_capture: bool,
435 #[arg(long, default_value_t = true, action = clap::ArgAction::Set, default_missing_value = "true", num_args = 0..=1, require_equals = true)]
436 verbose_run_make_subprocess_output: bool,
439 #[arg(long)]
440 test_codegen_backend: Option<CodegenBackendKind>,
442 #[arg(long)]
443 bypass_ignore_backends: bool,
445
446 #[arg(long)]
448 #[doc(hidden)]
449 no_doc: bool,
450
451 #[arg(long)]
455 record: bool,
456 #[arg(long)]
458 rerun: bool,
459 },
460 Miri {
462 #[arg(long)]
463 no_fail_fast: bool,
465 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
466 test_args: Vec<String>,
469 #[arg(long)]
470 all_targets: bool,
472 #[arg(long)]
473 doc: bool,
475 #[arg(long)]
477 tests: bool,
478
479 #[arg(long)]
481 #[doc(hidden)]
482 no_doc: bool,
483 },
484 Bench {
486 #[arg(long, allow_hyphen_values(true))]
487 test_args: Vec<String>,
488 },
489 Clean {
491 #[arg(long)]
492 all: bool,
494 #[arg(long, value_name = "N")]
495 stage: Option<u32>,
497 },
498 Dist,
500 Install,
502 #[command(visible_aliases = ["r"], long_about = "\n
503 Arguments:
504 This subcommand accepts a number of paths to tools to build and run. For
505 example:
506 ./x.py run src/tools/bump-stage0
507 At least a tool needs to be called.")]
508 Run {
510 #[arg(long, allow_hyphen_values(true))]
512 args: Vec<String>,
513 },
514 #[command(long_about = format!(
516 "\n
517x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
518as well as setting up a git pre-push hook, VS Code config and toolchain link.
519Arguments:
520 This subcommand accepts a 'profile' to use for builds. For example:
521 ./x.py setup library
522 The profile is optional and you will be prompted interactively if it is not given.
523 The following profiles are available:
524{}
525 To only set up the git hook, editor config or toolchain link, you may use
526 ./x.py setup hook
527 ./x.py setup editor
528 ./x.py setup link", Profile::all_for_help(" ").trim_end()))]
529 Setup {
530 #[arg(value_name = "<PROFILE>|hook|editor|link")]
533 profile: Option<PathBuf>,
534 },
535 Vendor {
537 #[arg(long)]
539 sync: Vec<PathBuf>,
540 #[arg(long)]
542 versioned_dirs: bool,
543 },
544 Perf(PerfArgs),
546}
547
548impl Default for Subcommand {
549 fn default() -> Self {
550 Subcommand::Build { timings: false }
551 }
552}
553
554impl Subcommand {
555 pub fn compiletest_rustc_args(&self) -> Vec<&str> {
556 match *self {
557 Subcommand::Test { ref compiletest_rustc_args, .. } => {
558 compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
559 }
560 _ => vec![],
561 }
562 }
563
564 pub fn fail_fast(&self) -> bool {
565 match *self {
566 Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
567 !no_fail_fast
568 }
569 _ => false,
570 }
571 }
572
573 pub fn test_target(&self) -> TestTarget {
574 match *self {
575 Subcommand::Test { mut all_targets, doc, tests, no_doc, .. }
576 | Subcommand::Miri { mut all_targets, doc, tests, no_doc, .. } => {
577 all_targets = all_targets || no_doc;
579
580 match (all_targets, doc, tests) {
581 (true, true, _) | (true, _, true) | (_, true, true) => {
582 panic!("You can only set one of `--all-targets`, `--doc` and `--tests`.")
583 }
584 (true, false, false) => TestTarget::AllTargets,
585 (false, true, false) => TestTarget::DocOnly,
586 (false, false, true) => TestTarget::Tests,
587 (false, false, false) => TestTarget::Default,
588 }
589 }
590 _ => TestTarget::Default,
591 }
592 }
593
594 pub fn no_doc(&self) -> bool {
595 match *self {
596 Subcommand::Test { no_doc, .. } | Subcommand::Miri { no_doc, .. } => no_doc,
597 _ => false,
598 }
599 }
600
601 pub fn bless(&self) -> bool {
602 match *self {
603 Subcommand::Test { bless, .. } => bless,
604 _ => false,
605 }
606 }
607
608 pub fn extra_checks(&self) -> Option<&str> {
609 match *self {
610 Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
611 _ => None,
612 }
613 }
614
615 pub fn only_modified(&self) -> bool {
616 match *self {
617 Subcommand::Test { only_modified, .. } => only_modified,
618 _ => false,
619 }
620 }
621
622 pub fn force_rerun(&self) -> bool {
623 match *self {
624 Subcommand::Test { force_rerun, .. } => force_rerun,
625 _ => false,
626 }
627 }
628
629 pub fn no_capture(&self) -> bool {
630 match *self {
631 Subcommand::Test { no_capture, .. } => no_capture,
632 _ => false,
633 }
634 }
635
636 pub fn verbose_run_make_subprocess_output(&self) -> bool {
637 match *self {
638 Subcommand::Test { verbose_run_make_subprocess_output, .. } => {
639 verbose_run_make_subprocess_output
640 }
641 _ => true,
642 }
643 }
644
645 pub fn rustfix_coverage(&self) -> bool {
646 match *self {
647 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
648 _ => false,
649 }
650 }
651
652 pub fn compare_mode(&self) -> Option<&str> {
653 match *self {
654 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
655 _ => None,
656 }
657 }
658
659 pub fn pass(&self) -> Option<&str> {
660 match *self {
661 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
662 _ => None,
663 }
664 }
665
666 pub fn run(&self) -> Option<&str> {
667 match *self {
668 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
669 _ => None,
670 }
671 }
672
673 pub fn open(&self) -> bool {
674 match *self {
675 Subcommand::Doc { open, .. } => open,
676 _ => false,
677 }
678 }
679
680 pub fn json(&self) -> bool {
681 match *self {
682 Subcommand::Doc { json, .. } => json,
683 _ => false,
684 }
685 }
686
687 pub fn timings(&self) -> bool {
688 match *self {
689 Subcommand::Build { timings, .. } | Subcommand::Check { timings, .. } => timings,
690 _ => false,
691 }
692 }
693
694 pub fn vendor_versioned_dirs(&self) -> bool {
695 match *self {
696 Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
697 _ => false,
698 }
699 }
700
701 pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
702 match self {
703 Subcommand::Vendor { sync, .. } => sync.clone(),
704 _ => vec![],
705 }
706 }
707
708 pub fn test_codegen_backend(&self) -> Option<&CodegenBackendKind> {
709 match self {
710 Subcommand::Test { test_codegen_backend, .. } => test_codegen_backend.as_ref(),
711 _ => None,
712 }
713 }
714
715 pub fn bypass_ignore_backends(&self) -> bool {
716 match self {
717 Subcommand::Test { bypass_ignore_backends, .. } => *bypass_ignore_backends,
718 _ => false,
719 }
720 }
721
722 pub fn record(&self) -> bool {
723 match self {
724 Subcommand::Test { record, .. } => *record,
725 _ => false,
726 }
727 }
728
729 pub fn rerun(&self) -> bool {
730 match self {
731 Subcommand::Test { rerun, .. } => *rerun,
732 _ => false,
733 }
734 }
735}
736
737pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option<String> {
740 let mut cmd = Flags::command();
741 let current = if !path.exists() {
742 String::new()
743 } else {
744 std::fs::read_to_string(path).unwrap_or_else(|_| {
745 eprintln!("couldn't read {}", path.display());
746 helpers::exit_process(1);
747 })
748 };
749 let mut buf = Vec::new();
750 let (bin_name, _) = path
751 .file_name()
752 .expect("path should be a regular file")
753 .to_str()
754 .expect("file name should be UTF-8")
755 .rsplit_once('.')
756 .expect("file name should have an extension");
757
758 cmd.set_bin_name(bin_name);
761 cmd.build();
762 shell.generate(&cmd, &mut buf);
763 if buf == current.as_bytes() {
764 return None;
765 }
766 Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
767}
768
769pub fn top_level_help() -> String {
771 let mut cmd = Flags::command();
772 cmd.render_help().to_string()
773}