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;
16use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
17use crate::{Build, DocTests};
18
19#[derive(Copy, Clone, Default, Debug, ValueEnum)]
20pub enum Color {
21 Always,
22 Never,
23 #[default]
24 Auto,
25}
26
27#[derive(Copy, Clone, Default, Debug, ValueEnum)]
29pub enum Warnings {
30 Deny,
31 Warn,
32 #[default]
33 Default,
34}
35
36#[derive(Debug, Parser)]
38#[command(
39 override_usage = "x.py <subcommand> [options] [<paths>...]",
40 disable_help_subcommand(true),
41 about = "",
42 next_line_help(false)
43)]
44pub struct Flags {
45 #[command(subcommand)]
46 pub cmd: Subcommand,
47
48 #[arg(global = true, short, long, action = clap::ArgAction::Count)]
49 pub verbose: u8, #[arg(global = true, short, long)]
52 pub incremental: bool,
54 #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
55 pub config: Option<PathBuf>,
57 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
58 pub build_dir: Option<PathBuf>,
60
61 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
62 pub build: Option<String>,
64
65 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
66 pub host: Option<TargetSelectionList>,
68
69 #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
70 pub target: Option<TargetSelectionList>,
72
73 #[arg(global = true, long, value_name = "PATH")]
74 pub exclude: Vec<PathBuf>, #[arg(global = true, long, value_name = "PATH")]
77 pub skip: Vec<PathBuf>,
79 #[arg(global = true, long)]
80 pub include_default_paths: bool,
82
83 #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
85 pub rustc_error_format: Option<String>,
86
87 #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
88 pub on_fail: Option<String>,
90 #[arg(global = true, long)]
91 pub dry_run: bool,
93 #[arg(global = true, long)]
95 pub dump_bootstrap_shims: bool,
96 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
97 pub stage: Option<u32>,
100
101 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
102 pub keep_stage: Vec<u32>,
105 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
106 pub keep_stage_std: Vec<u32>,
109 #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
110 pub src: Option<PathBuf>,
112
113 #[arg(
114 global = true,
115 short,
116 long,
117 value_hint = clap::ValueHint::Other,
118 value_name = "JOBS"
119 )]
120 pub jobs: Option<u32>,
122 #[arg(global = true, long)]
125 #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
126 pub warnings: Warnings,
130
131 #[arg(global = true, long)]
132 pub json_output: bool,
134
135 #[arg(global = true, long, value_name = "STYLE")]
136 #[arg(value_enum, default_value_t = Color::Auto)]
137 pub color: Color,
139
140 #[arg(global = true, long)]
141 pub bypass_bootstrap_lock: bool,
146
147 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
149 pub rust_profile_generate: Option<String>,
150 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
152 pub rust_profile_use: Option<String>,
153 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
155 pub llvm_profile_use: Option<String>,
156 #[arg(global = true, long)]
162 pub llvm_profile_generate: bool,
163 #[arg(global = true, long)]
165 pub enable_bolt_settings: bool,
166 #[arg(global = true, long)]
168 pub skip_stage0_validation: bool,
169 #[arg(global = true, long)]
171 pub reproducible_artifact: Vec<String>,
172 #[arg(global = true)]
173 pub paths: Vec<PathBuf>,
175 #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
177 pub set: Vec<String>,
178 #[arg(global = true, last(true), value_name = "ARGS")]
180 pub free_args: Vec<String>,
181 #[arg(global = true, long, value_name = "bool")]
183 pub ci: Option<bool>,
184 #[arg(global = true, long)]
188 pub skip_std_check_if_no_download_rustc: bool,
189}
190
191impl Flags {
192 pub fn try_parse_verbose_help(args: &[String]) -> bool {
195 #[derive(Parser)]
197 #[command(disable_help_flag(true))]
198 struct HelpVerboseOnly {
199 #[arg(short, long)]
200 help: bool,
201 #[arg(global = true, short, long, action = clap::ArgAction::Count)]
202 pub verbose: u8,
203 #[arg(value_enum)]
204 cmd: Kind,
205 }
206 if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
207 HelpVerboseOnly::try_parse_from(normalize_args(args))
208 {
209 println!("NOTE: updating submodules before printing available paths");
210 let flags = Self::parse(&[String::from("build")]);
211 let config = Config::parse(flags);
212 let build = Build::new(config);
213 let paths = Builder::get_help(&build, subcommand);
214 if let Some(s) = paths {
215 println!("{s}");
216 } else {
217 panic!("No paths available for subcommand `{}`", subcommand.as_str());
218 }
219 true
220 } else {
221 false
222 }
223 }
224
225 #[cfg_attr(
226 feature = "tracing",
227 instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
228 )]
229 pub fn parse(args: &[String]) -> Self {
230 Flags::parse_from(normalize_args(args))
231 }
232}
233
234fn normalize_args(args: &[String]) -> Vec<String> {
235 let first = String::from("x.py");
236 let it = std::iter::once(first).chain(args.iter().cloned());
237 it.collect()
238}
239
240#[derive(Debug, Clone, Default, clap::Subcommand)]
241pub enum Subcommand {
242 #[command(aliases = ["b"], long_about = "\n
243 Arguments:
244 This subcommand accepts a number of paths to directories to the crates
245 and/or artifacts to compile. For example, for a quick build of a usable
246 compiler:
247 ./x.py build --stage 1 library/std
248 This will build a compiler and standard library from the local source code.
249 Once this is done, build/$ARCH/stage1 contains a usable compiler.
250 If no arguments are passed then the default artifacts for that stage are
251 compiled. For example:
252 ./x.py build --stage 0
253 ./x.py build ")]
254 #[default]
256 Build,
257 #[command(aliases = ["c"], long_about = "\n
258 Arguments:
259 This subcommand accepts a number of paths to directories to the crates
260 and/or artifacts to compile. For example:
261 ./x.py check library/std
262 If no arguments are passed then many artifacts are checked.")]
263 Check {
265 #[arg(long)]
266 all_targets: bool,
268 },
269 #[command(long_about = "\n
271 Arguments:
272 This subcommand accepts a number of paths to directories to the crates
273 and/or artifacts to run clippy against. For example:
274 ./x.py clippy library/core
275 ./x.py clippy library/core library/proc_macro")]
276 Clippy {
277 #[arg(long)]
278 fix: bool,
279 #[arg(long, requires = "fix")]
280 allow_dirty: bool,
281 #[arg(long, requires = "fix")]
282 allow_staged: bool,
283 #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
285 allow: Vec<String>,
286 #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
288 deny: Vec<String>,
289 #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
291 warn: Vec<String>,
292 #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
294 forbid: Vec<String>,
295 },
296 #[command(long_about = "\n
298 Arguments:
299 This subcommand accepts a number of paths to directories to the crates
300 and/or artifacts to run `cargo fix` against. For example:
301 ./x.py fix library/core
302 ./x.py fix library/core library/proc_macro")]
303 Fix,
304 #[command(
305 name = "fmt",
306 long_about = "\n
307 Arguments:
308 This subcommand optionally accepts a `--check` flag which succeeds if
309 formatting is correct and fails if it is not. For example:
310 ./x.py fmt
311 ./x.py fmt --check"
312 )]
313 Format {
315 #[arg(long)]
317 check: bool,
318
319 #[arg(long)]
321 all: bool,
322 },
323 #[command(aliases = ["d"], long_about = "\n
324 Arguments:
325 This subcommand accepts a number of paths to directories of documentation
326 to build. For example:
327 ./x.py doc src/doc/book
328 ./x.py doc src/doc/nomicon
329 ./x.py doc src/doc/book library/std
330 ./x.py doc library/std --json
331 ./x.py doc library/std --open
332 If no arguments are passed then everything is documented:
333 ./x.py doc
334 ./x.py doc --stage 1")]
335 Doc {
337 #[arg(long)]
338 open: bool,
340 #[arg(long)]
341 json: bool,
343 },
344 #[command(aliases = ["t"], long_about = "\n
345 Arguments:
346 This subcommand accepts a number of paths to test directories that
347 should be compiled and run. For example:
348 ./x.py test tests/ui
349 ./x.py test library/std --test-args hash_map
350 ./x.py test library/std --stage 0 --no-doc
351 ./x.py test tests/ui --bless
352 ./x.py test tests/ui --compare-mode next-solver
353 Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
354 just like `build library/std --stage N` it tests the compiler produced by the previous
355 stage.
356 Execute tool tests with a tool name argument:
357 ./x.py test tidy
358 If no arguments are passed then the complete artifacts for that stage are
359 compiled and tested.
360 ./x.py test
361 ./x.py test --stage 1")]
362 Test {
364 #[arg(long)]
365 no_fail_fast: bool,
367 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
368 test_args: Vec<String>,
371 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
373 compiletest_rustc_args: Vec<String>,
374 #[arg(long)]
375 no_doc: bool,
377 #[arg(long)]
378 doc: bool,
380 #[arg(long)]
381 bless: bool,
383 #[arg(long)]
384 extra_checks: Option<String>,
387 #[arg(long)]
388 force_rerun: bool,
390 #[arg(long)]
391 only_modified: bool,
393 #[arg(long, value_name = "COMPARE MODE")]
394 compare_mode: Option<String>,
396 #[arg(long, value_name = "check | build | run")]
397 pass: Option<String>,
399 #[arg(long, value_name = "auto | always | never")]
400 run: Option<String>,
402 #[arg(long)]
403 rustfix_coverage: bool,
406 #[arg(long)]
407 no_capture: bool,
409 },
410 Miri {
412 #[arg(long)]
413 no_fail_fast: bool,
415 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
416 test_args: Vec<String>,
419 #[arg(long)]
420 no_doc: bool,
422 #[arg(long)]
423 doc: bool,
425 },
426 Bench {
428 #[arg(long, allow_hyphen_values(true))]
429 test_args: Vec<String>,
430 },
431 Clean {
433 #[arg(long)]
434 all: bool,
436 #[arg(long, value_name = "N")]
437 stage: Option<u32>,
439 },
440 Dist,
442 Install,
444 #[command(aliases = ["r"], long_about = "\n
445 Arguments:
446 This subcommand accepts a number of paths to tools to build and run. For
447 example:
448 ./x.py run src/tools/bump-stage0
449 At least a tool needs to be called.")]
450 Run {
452 #[arg(long, allow_hyphen_values(true))]
454 args: Vec<String>,
455 },
456 #[command(long_about = format!(
458 "\n
459x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
460as well as setting up a git pre-push hook, VS Code config and toolchain link.
461Arguments:
462 This subcommand accepts a 'profile' to use for builds. For example:
463 ./x.py setup library
464 The profile is optional and you will be prompted interactively if it is not given.
465 The following profiles are available:
466{}
467 To only set up the git hook, editor config or toolchain link, you may use
468 ./x.py setup hook
469 ./x.py setup editor
470 ./x.py setup link", Profile::all_for_help(" ").trim_end()))]
471 Setup {
472 #[arg(value_name = "<PROFILE>|hook|editor|link")]
475 profile: Option<PathBuf>,
476 },
477 #[command(long_about = "\n")]
479 Suggest {
480 #[arg(long)]
482 run: bool,
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::Suggest { .. } => Kind::Suggest,
515 Subcommand::Vendor { .. } => Kind::Vendor,
516 Subcommand::Perf { .. } => Kind::Perf,
517 }
518 }
519
520 pub fn compiletest_rustc_args(&self) -> Vec<&str> {
521 match *self {
522 Subcommand::Test { ref compiletest_rustc_args, .. } => {
523 compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
524 }
525 _ => vec![],
526 }
527 }
528
529 pub fn fail_fast(&self) -> bool {
530 match *self {
531 Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
532 !no_fail_fast
533 }
534 _ => false,
535 }
536 }
537
538 pub fn doc_tests(&self) -> DocTests {
539 match *self {
540 Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
541 if doc {
542 DocTests::Only
543 } else if no_doc {
544 DocTests::No
545 } else {
546 DocTests::Yes
547 }
548 }
549 _ => DocTests::Yes,
550 }
551 }
552
553 pub fn bless(&self) -> bool {
554 match *self {
555 Subcommand::Test { bless, .. } => bless,
556 _ => false,
557 }
558 }
559
560 pub fn extra_checks(&self) -> Option<&str> {
561 match *self {
562 Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
563 _ => None,
564 }
565 }
566
567 pub fn only_modified(&self) -> bool {
568 match *self {
569 Subcommand::Test { only_modified, .. } => only_modified,
570 _ => false,
571 }
572 }
573
574 pub fn force_rerun(&self) -> bool {
575 match *self {
576 Subcommand::Test { force_rerun, .. } => force_rerun,
577 _ => false,
578 }
579 }
580
581 pub fn no_capture(&self) -> bool {
582 match *self {
583 Subcommand::Test { no_capture, .. } => no_capture,
584 _ => false,
585 }
586 }
587
588 pub fn rustfix_coverage(&self) -> bool {
589 match *self {
590 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
591 _ => false,
592 }
593 }
594
595 pub fn compare_mode(&self) -> Option<&str> {
596 match *self {
597 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
598 _ => None,
599 }
600 }
601
602 pub fn pass(&self) -> Option<&str> {
603 match *self {
604 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
605 _ => None,
606 }
607 }
608
609 pub fn run(&self) -> Option<&str> {
610 match *self {
611 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
612 _ => None,
613 }
614 }
615
616 pub fn open(&self) -> bool {
617 match *self {
618 Subcommand::Doc { open, .. } => open,
619 _ => false,
620 }
621 }
622
623 pub fn json(&self) -> bool {
624 match *self {
625 Subcommand::Doc { json, .. } => json,
626 _ => false,
627 }
628 }
629
630 pub fn vendor_versioned_dirs(&self) -> bool {
631 match *self {
632 Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
633 _ => false,
634 }
635 }
636
637 pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
638 match self {
639 Subcommand::Vendor { sync, .. } => sync.clone(),
640 _ => vec![],
641 }
642 }
643}
644
645pub fn get_completion<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
648 let mut cmd = Flags::command();
649 let current = if !path.exists() {
650 String::new()
651 } else {
652 std::fs::read_to_string(path).unwrap_or_else(|_| {
653 eprintln!("couldn't read {}", path.display());
654 crate::exit!(1)
655 })
656 };
657 let mut buf = Vec::new();
658 let (bin_name, _) = path
659 .file_name()
660 .expect("path should be a regular file")
661 .to_str()
662 .expect("file name should be UTF-8")
663 .rsplit_once('.')
664 .expect("file name should have an extension");
665 clap_complete::generate(shell, &mut cmd, bin_name, &mut buf);
666 if buf == current.as_bytes() {
667 return None;
668 }
669 Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
670}