1use 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#[derive(Copy, Clone, Default, Debug, ValueEnum)]
28pub enum Warnings {
29 Deny,
30 Warn,
31 #[default]
32 Default,
33}
34
35#[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 pub verbose: u8, #[arg(global = true, short, long)]
51 pub incremental: bool,
53 #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
54 pub config: Option<PathBuf>,
56 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
57 pub build_dir: Option<PathBuf>,
59
60 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
61 pub build: Option<String>,
63
64 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
65 pub host: Option<TargetSelectionList>,
67
68 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
69 pub target: Option<TargetSelectionList>,
71
72 #[arg(global = true, long, value_name = "PATH")]
73 pub exclude: Vec<PathBuf>, #[arg(global = true, long, value_name = "PATH")]
76 pub skip: Vec<PathBuf>,
78 #[arg(global = true, long)]
79 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 pub on_fail: Option<String>,
88 #[arg(global = true, long)]
89 pub dry_run: bool,
91 #[arg(global = true, long)]
93 pub dump_bootstrap_shims: bool,
94 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
95 pub stage: Option<u32>,
98
99 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
100 pub keep_stage: Vec<u32>,
103 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
104 pub keep_stage_std: Vec<u32>,
107 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
108 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 pub jobs: Option<u32>,
120 #[arg(global = true, long)]
123 #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
124 pub warnings: Warnings,
128
129 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
130 pub error_format: Option<String>,
132 #[arg(global = true, long)]
133 pub json_output: bool,
135
136 #[arg(global = true, long, value_name = "STYLE")]
137 #[arg(value_enum, default_value_t = Color::Auto)]
138 pub color: Color,
140
141 #[arg(global = true, long)]
142 pub bypass_bootstrap_lock: bool,
147
148 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
150 pub rust_profile_generate: Option<String>,
151 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
153 pub rust_profile_use: Option<String>,
154 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
156 pub llvm_profile_use: Option<String>,
157 #[arg(global = true, long)]
163 pub llvm_profile_generate: bool,
164 #[arg(global = true, long)]
166 pub enable_bolt_settings: bool,
167 #[arg(global = true, long)]
169 pub skip_stage0_validation: bool,
170 #[arg(global = true, long)]
172 pub reproducible_artifact: Vec<String>,
173 #[arg(global = true)]
174 pub paths: Vec<PathBuf>,
176 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
178 pub set: Vec<String>,
179 #[arg(global = true, last(true), value_name = "ARGS")]
181 pub free_args: Vec<String>,
182 #[arg(global = true, long, value_name = "bool")]
184 pub ci: Option<bool>,
185}
186
187impl Flags {
188 pub fn try_parse_verbose_help(args: &[String]) -> bool {
191 #[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 #[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 Check {
260 #[arg(long)]
261 all_targets: bool,
263 },
264 #[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 #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
280 allow: Vec<String>,
281 #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
283 deny: Vec<String>,
284 #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
286 warn: Vec<String>,
287 #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
289 forbid: Vec<String>,
290 },
291 #[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 Format {
310 #[arg(long)]
312 check: bool,
313
314 #[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 Doc {
332 #[arg(long)]
333 open: bool,
335 #[arg(long)]
336 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 Test {
359 #[arg(long)]
360 no_fail_fast: bool,
362 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
363 test_args: Vec<String>,
366 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
368 compiletest_rustc_args: Vec<String>,
369 #[arg(long)]
370 no_doc: bool,
372 #[arg(long)]
373 doc: bool,
375 #[arg(long)]
376 bless: bool,
378 #[arg(long)]
379 extra_checks: Option<String>,
382 #[arg(long)]
383 force_rerun: bool,
385 #[arg(long)]
386 only_modified: bool,
388 #[arg(long, value_name = "COMPARE MODE")]
389 compare_mode: Option<String>,
391 #[arg(long, value_name = "check | build | run")]
392 pass: Option<String>,
394 #[arg(long, value_name = "auto | always | never")]
395 run: Option<String>,
397 #[arg(long)]
398 rustfix_coverage: bool,
401 #[arg(long)]
402 no_capture: bool,
404 },
405 Miri {
407 #[arg(long)]
408 no_fail_fast: bool,
410 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
411 test_args: Vec<String>,
414 #[arg(long)]
415 no_doc: bool,
417 #[arg(long)]
418 doc: bool,
420 },
421 Bench {
423 #[arg(long, allow_hyphen_values(true))]
424 test_args: Vec<String>,
425 },
426 Clean {
428 #[arg(long)]
429 all: bool,
431 #[arg(long, value_name = "N")]
432 stage: Option<u32>,
434 },
435 Dist,
437 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 {
447 #[arg(long, allow_hyphen_values(true))]
449 args: Vec<String>,
450 },
451 #[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 #[arg(value_name = "<PROFILE>|hook|editor|link")]
470 profile: Option<PathBuf>,
471 },
472 #[command(long_about = "\n")]
474 Suggest {
475 #[arg(long)]
477 run: bool,
478 },
479 Vendor {
481 #[arg(long)]
483 sync: Vec<PathBuf>,
484 #[arg(long)]
486 versioned_dirs: bool,
487 },
488 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
640pub 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}