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