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
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 #[arg(global = true, long)]
189 pub skip_std_check_if_no_download_rustc: bool,
190}
191
192impl Flags {
193 pub fn try_parse_verbose_help(args: &[String]) -> bool {
196 #[derive(Parser)]
198 #[command(disable_help_flag(true))]
199 struct HelpVerboseOnly {
200 #[arg(short, long)]
201 help: bool,
202 #[arg(global = true, short, long, action = clap::ArgAction::Count)]
203 pub verbose: u8,
204 #[arg(value_enum)]
205 cmd: Kind,
206 }
207 if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
208 HelpVerboseOnly::try_parse_from(normalize_args(args))
209 {
210 println!("NOTE: updating submodules before printing available paths");
211 let flags = Self::parse(&[String::from("build")]);
212 let config = Config::parse(flags);
213 let build = Build::new(config);
214 let paths = Builder::get_help(&build, subcommand);
215 if let Some(s) = paths {
216 println!("{s}");
217 } else {
218 panic!("No paths available for subcommand `{}`", subcommand.as_str());
219 }
220 true
221 } else {
222 false
223 }
224 }
225
226 #[cfg_attr(
227 feature = "tracing",
228 instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
229 )]
230 pub fn parse(args: &[String]) -> Self {
231 Flags::parse_from(normalize_args(args))
232 }
233}
234
235fn normalize_args(args: &[String]) -> Vec<String> {
236 let first = String::from("x.py");
237 let it = std::iter::once(first).chain(args.iter().cloned());
238 it.collect()
239}
240
241#[derive(Debug, Clone, Default, clap::Subcommand)]
242pub enum Subcommand {
243 #[command(aliases = ["b"], long_about = "\n
244 Arguments:
245 This subcommand accepts a number of paths to directories to the crates
246 and/or artifacts to compile. For example, for a quick build of a usable
247 compiler:
248 ./x.py build --stage 1 library/std
249 This will build a compiler and standard library from the local source code.
250 Once this is done, build/$ARCH/stage1 contains a usable compiler.
251 If no arguments are passed then the default artifacts for that stage are
252 compiled. For example:
253 ./x.py build --stage 0
254 ./x.py build ")]
255 #[default]
257 Build,
258 #[command(aliases = ["c"], long_about = "\n
259 Arguments:
260 This subcommand accepts a number of paths to directories to the crates
261 and/or artifacts to compile. For example:
262 ./x.py check library/std
263 If no arguments are passed then many artifacts are checked.")]
264 Check {
266 #[arg(long)]
267 all_targets: bool,
269 },
270 #[command(long_about = "\n
272 Arguments:
273 This subcommand accepts a number of paths to directories to the crates
274 and/or artifacts to run clippy against. For example:
275 ./x.py clippy library/core
276 ./x.py clippy library/core library/proc_macro")]
277 Clippy {
278 #[arg(long)]
279 fix: bool,
280 #[arg(long, requires = "fix")]
281 allow_dirty: bool,
282 #[arg(long, requires = "fix")]
283 allow_staged: bool,
284 #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
286 allow: Vec<String>,
287 #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
289 deny: Vec<String>,
290 #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
292 warn: Vec<String>,
293 #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
295 forbid: Vec<String>,
296 },
297 #[command(long_about = "\n
299 Arguments:
300 This subcommand accepts a number of paths to directories to the crates
301 and/or artifacts to run `cargo fix` against. For example:
302 ./x.py fix library/core
303 ./x.py fix library/core library/proc_macro")]
304 Fix,
305 #[command(
306 name = "fmt",
307 long_about = "\n
308 Arguments:
309 This subcommand optionally accepts a `--check` flag which succeeds if
310 formatting is correct and fails if it is not. For example:
311 ./x.py fmt
312 ./x.py fmt --check"
313 )]
314 Format {
316 #[arg(long)]
318 check: bool,
319
320 #[arg(long)]
322 all: bool,
323 },
324 #[command(aliases = ["d"], long_about = "\n
325 Arguments:
326 This subcommand accepts a number of paths to directories of documentation
327 to build. For example:
328 ./x.py doc src/doc/book
329 ./x.py doc src/doc/nomicon
330 ./x.py doc src/doc/book library/std
331 ./x.py doc library/std --json
332 ./x.py doc library/std --open
333 If no arguments are passed then everything is documented:
334 ./x.py doc
335 ./x.py doc --stage 1")]
336 Doc {
338 #[arg(long)]
339 open: bool,
341 #[arg(long)]
342 json: bool,
344 },
345 #[command(aliases = ["t"], long_about = "\n
346 Arguments:
347 This subcommand accepts a number of paths to test directories that
348 should be compiled and run. For example:
349 ./x.py test tests/ui
350 ./x.py test library/std --test-args hash_map
351 ./x.py test library/std --stage 0 --no-doc
352 ./x.py test tests/ui --bless
353 ./x.py test tests/ui --compare-mode next-solver
354 Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
355 just like `build library/std --stage N` it tests the compiler produced by the previous
356 stage.
357 Execute tool tests with a tool name argument:
358 ./x.py test tidy
359 If no arguments are passed then the complete artifacts for that stage are
360 compiled and tested.
361 ./x.py test
362 ./x.py test --stage 1")]
363 Test {
365 #[arg(long)]
366 no_fail_fast: bool,
368 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
369 test_args: Vec<String>,
372 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
374 compiletest_rustc_args: Vec<String>,
375 #[arg(long)]
376 no_doc: bool,
378 #[arg(long)]
379 doc: bool,
381 #[arg(long)]
382 bless: bool,
384 #[arg(long)]
385 extra_checks: Option<String>,
388 #[arg(long)]
389 force_rerun: bool,
391 #[arg(long)]
392 only_modified: bool,
394 #[arg(long, value_name = "COMPARE MODE")]
395 compare_mode: Option<String>,
397 #[arg(long, value_name = "check | build | run")]
398 pass: Option<String>,
400 #[arg(long, value_name = "auto | always | never")]
401 run: Option<String>,
403 #[arg(long)]
404 rustfix_coverage: bool,
407 #[arg(long)]
408 no_capture: bool,
410 },
411 Miri {
413 #[arg(long)]
414 no_fail_fast: bool,
416 #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
417 test_args: Vec<String>,
420 #[arg(long)]
421 no_doc: bool,
423 #[arg(long)]
424 doc: bool,
426 },
427 Bench {
429 #[arg(long, allow_hyphen_values(true))]
430 test_args: Vec<String>,
431 },
432 Clean {
434 #[arg(long)]
435 all: bool,
437 #[arg(long, value_name = "N")]
438 stage: Option<u32>,
440 },
441 Dist,
443 Install,
445 #[command(aliases = ["r"], long_about = "\n
446 Arguments:
447 This subcommand accepts a number of paths to tools to build and run. For
448 example:
449 ./x.py run src/tools/bump-stage0
450 At least a tool needs to be called.")]
451 Run {
453 #[arg(long, allow_hyphen_values(true))]
455 args: Vec<String>,
456 },
457 #[command(long_about = format!(
459 "\n
460x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
461as well as setting up a git pre-push hook, VS Code config and toolchain link.
462Arguments:
463 This subcommand accepts a 'profile' to use for builds. For example:
464 ./x.py setup library
465 The profile is optional and you will be prompted interactively if it is not given.
466 The following profiles are available:
467{}
468 To only set up the git hook, editor config or toolchain link, you may use
469 ./x.py setup hook
470 ./x.py setup editor
471 ./x.py setup link", Profile::all_for_help(" ").trim_end()))]
472 Setup {
473 #[arg(value_name = "<PROFILE>|hook|editor|link")]
476 profile: Option<PathBuf>,
477 },
478 #[command(long_about = "\n")]
480 Suggest {
481 #[arg(long)]
483 run: bool,
484 },
485 Vendor {
487 #[arg(long)]
489 sync: Vec<PathBuf>,
490 #[arg(long)]
492 versioned_dirs: bool,
493 },
494 Perf(PerfArgs),
496}
497
498impl Subcommand {
499 pub fn kind(&self) -> Kind {
500 match self {
501 Subcommand::Bench { .. } => Kind::Bench,
502 Subcommand::Build => Kind::Build,
503 Subcommand::Check { .. } => Kind::Check,
504 Subcommand::Clippy { .. } => Kind::Clippy,
505 Subcommand::Doc { .. } => Kind::Doc,
506 Subcommand::Fix => Kind::Fix,
507 Subcommand::Format { .. } => Kind::Format,
508 Subcommand::Test { .. } => Kind::Test,
509 Subcommand::Miri { .. } => Kind::Miri,
510 Subcommand::Clean { .. } => Kind::Clean,
511 Subcommand::Dist => Kind::Dist,
512 Subcommand::Install => Kind::Install,
513 Subcommand::Run { .. } => Kind::Run,
514 Subcommand::Setup { .. } => Kind::Setup,
515 Subcommand::Suggest { .. } => Kind::Suggest,
516 Subcommand::Vendor { .. } => Kind::Vendor,
517 Subcommand::Perf { .. } => Kind::Perf,
518 }
519 }
520
521 pub fn compiletest_rustc_args(&self) -> Vec<&str> {
522 match *self {
523 Subcommand::Test { ref compiletest_rustc_args, .. } => {
524 compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
525 }
526 _ => vec![],
527 }
528 }
529
530 pub fn fail_fast(&self) -> bool {
531 match *self {
532 Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
533 !no_fail_fast
534 }
535 _ => false,
536 }
537 }
538
539 pub fn doc_tests(&self) -> DocTests {
540 match *self {
541 Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
542 if doc {
543 DocTests::Only
544 } else if no_doc {
545 DocTests::No
546 } else {
547 DocTests::Yes
548 }
549 }
550 _ => DocTests::Yes,
551 }
552 }
553
554 pub fn bless(&self) -> bool {
555 match *self {
556 Subcommand::Test { bless, .. } => bless,
557 _ => false,
558 }
559 }
560
561 pub fn extra_checks(&self) -> Option<&str> {
562 match *self {
563 Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
564 _ => None,
565 }
566 }
567
568 pub fn only_modified(&self) -> bool {
569 match *self {
570 Subcommand::Test { only_modified, .. } => only_modified,
571 _ => false,
572 }
573 }
574
575 pub fn force_rerun(&self) -> bool {
576 match *self {
577 Subcommand::Test { force_rerun, .. } => force_rerun,
578 _ => false,
579 }
580 }
581
582 pub fn no_capture(&self) -> bool {
583 match *self {
584 Subcommand::Test { no_capture, .. } => no_capture,
585 _ => false,
586 }
587 }
588
589 pub fn rustfix_coverage(&self) -> bool {
590 match *self {
591 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
592 _ => false,
593 }
594 }
595
596 pub fn compare_mode(&self) -> Option<&str> {
597 match *self {
598 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
599 _ => None,
600 }
601 }
602
603 pub fn pass(&self) -> Option<&str> {
604 match *self {
605 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
606 _ => None,
607 }
608 }
609
610 pub fn run(&self) -> Option<&str> {
611 match *self {
612 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
613 _ => None,
614 }
615 }
616
617 pub fn open(&self) -> bool {
618 match *self {
619 Subcommand::Doc { open, .. } => open,
620 _ => false,
621 }
622 }
623
624 pub fn json(&self) -> bool {
625 match *self {
626 Subcommand::Doc { json, .. } => json,
627 _ => false,
628 }
629 }
630
631 pub fn vendor_versioned_dirs(&self) -> bool {
632 match *self {
633 Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
634 _ => false,
635 }
636 }
637
638 pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
639 match self {
640 Subcommand::Vendor { sync, .. } => sync.clone(),
641 _ => vec![],
642 }
643 }
644}
645
646pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option<String> {
649 let mut cmd = Flags::command();
650 let current = if !path.exists() {
651 String::new()
652 } else {
653 std::fs::read_to_string(path).unwrap_or_else(|_| {
654 eprintln!("couldn't read {}", path.display());
655 crate::exit!(1)
656 })
657 };
658 let mut buf = Vec::new();
659 let (bin_name, _) = path
660 .file_name()
661 .expect("path should be a regular file")
662 .to_str()
663 .expect("file name should be UTF-8")
664 .rsplit_once('.')
665 .expect("file name should have an extension");
666
667 cmd.set_bin_name(bin_name);
670 cmd.build();
671 shell.generate(&cmd, &mut buf);
672 if buf == current.as_bytes() {
673 return None;
674 }
675 Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
676}