1 //! Command-line interface of the rustbuild build system.
3 //! This module implements the command-line parsing of the build system which
4 //! has various flags to configure how it's run.
7 use std::path::PathBuf;
13 use crate::builder::Builder;
14 use crate::config::{Config, TargetSelection};
15 use crate::setup::Profile;
16 use crate::{Build, DocTests};
24 impl Default for Color {
25 fn default() -> Self {
30 impl std::str::FromStr for Color {
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 match s.to_lowercase().as_str() {
35 "always" => Ok(Self::Always),
36 "never" => Ok(Self::Never),
37 "auto" => Ok(Self::Auto),
43 /// Deserialized version of all flags for this compile.
45 pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
46 pub on_fail: Option<String>,
47 pub stage: Option<u32>,
48 pub keep_stage: Vec<u32>,
49 pub keep_stage_std: Vec<u32>,
51 pub host: Option<Vec<TargetSelection>>,
52 pub target: Option<Vec<TargetSelection>>,
53 pub config: Option<PathBuf>,
54 pub jobs: Option<u32>,
56 pub incremental: bool,
57 pub exclude: Vec<PathBuf>,
58 pub include_default_paths: bool,
59 pub rustc_error_format: Option<String>,
60 pub json_output: bool,
64 // This overrides the deny-warnings configuration option,
65 // which passes -Dwarnings to the compiler invocations.
67 // true => deny, false => warn
68 pub deny_warnings: Option<bool>,
70 pub llvm_skip_rebuild: Option<bool>,
72 pub rust_profile_use: Option<String>,
73 pub rust_profile_generate: Option<String>,
81 // Whether to run checking over all targets (e.g., unit / integration
103 /// Whether to automatically update stderr/stdout files
105 compare_mode: Option<String>,
106 pass: Option<String>,
108 test_args: Vec<String>,
109 rustc_args: Vec<String>,
112 rustfix_coverage: bool,
116 test_args: Vec<String>,
135 impl Default for Subcommand {
136 fn default() -> Subcommand {
137 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
142 pub fn parse(args: &[String]) -> Flags {
143 let mut subcommand_help = String::from(
145 Usage: x.py <subcommand> [options] [<paths>...]
148 build, b Compile either the compiler or libraries
149 check, c Compile either the compiler or libraries, using cargo check
150 clippy Run clippy (uses rustup/cargo-installed clippy binary)
153 test, t Build and run some test suites
154 bench Build and run some benchmarks
155 doc Build documentation
156 clean Clean out build directories
157 dist Build distribution artifacts
158 install Install distribution artifacts
159 run, r Run tools contained in this repository
160 setup Create a config.toml (making it easier to use `x.py` itself)
162 To learn more about a subcommand, run `./x.py <subcommand> -h`",
165 let mut opts = Options::new();
166 // Options common to all subcommands
167 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
168 opts.optflag("i", "incremental", "use incremental compilation");
169 opts.optopt("", "config", "TOML configuration file for build", "FILE");
170 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
171 opts.optmulti("", "host", "host targets to build", "HOST");
172 opts.optmulti("", "target", "target targets to build", "TARGET");
173 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
176 "include-default-paths",
177 "include default paths in addition to the provided ones",
179 opts.optopt("", "on-fail", "command to run on failure", "CMD");
180 opts.optflag("", "dry-run", "dry run; don't build anything");
184 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
185 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
191 "stage(s) to keep without recompiling \
192 (pass multiple times to keep e.g., both stages 0 and 1)",
198 "stage(s) of the standard library to keep without recompiling \
199 (pass multiple times to keep e.g., both stages 0 and 1)",
202 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
204 "number of jobs to run in parallel; \
205 defaults to {} (this host's logical CPU count)",
208 opts.optopt("j", "jobs", &j_msg, "JOBS");
209 opts.optflag("h", "help", "print this help message");
213 "if value is deny, will deny warnings, otherwise use default",
216 opts.optopt("", "error-format", "rustc error format", "FORMAT");
217 opts.optflag("", "json-output", "use message-format=json");
218 opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE");
222 "whether rebuilding llvm should be skipped \
223 a VALUE of TRUE indicates that llvm will not be rebuilt \
224 VALUE overrides the skip-rebuild option in config.toml.",
227 opts.optopt("", "rust-profile-generate", "generate PGO profile with rustc build", "FORMAT");
228 opts.optopt("", "rust-profile-use", "use PGO profile for rustc build", "FORMAT");
230 // We can't use getopt to parse the options until we have completed specifying which
231 // options are valid, but under the current implementation, some options are conditional on
232 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
233 // complete the definition of the options. Then we can use the getopt::Matches object from
235 let subcommand = args.iter().find(|&s| {
254 let subcommand = match subcommand {
257 // No or an invalid subcommand -- show the general usage and subcommand help
258 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
260 println!("{}\n", subcommand_help);
261 let exit_code = if args.is_empty() { 0 } else { 1 };
262 process::exit(exit_code);
266 // Some subcommands get extra options
267 match subcommand.as_str() {
269 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
273 "extra arguments to be passed for the test tool being used \
274 (e.g. libtest, compiletest or rustdoc)",
280 "extra options to pass the compiler when running tests",
283 opts.optflag("", "no-doc", "do not run doc tests");
284 opts.optflag("", "doc", "only run doc tests");
285 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
289 "mode describing what file the actual ui output will be compared to",
295 "force {check,build,run}-pass tests to this mode.",
296 "check | build | run",
298 opts.optopt("", "run", "whether to execute run-* tests", "auto | always | never");
302 "enable this to generate a Rustfix coverage file, which is saved in \
303 `/<build_base>/rustfix_missing_coverage.txt`",
307 opts.optflag("", "all-targets", "Check all targets");
310 opts.optmulti("", "test-args", "extra arguments", "ARGS");
313 opts.optflag("", "fix", "automatically apply lint suggestions");
316 opts.optflag("", "open", "open the docs in a browser");
319 opts.optflag("", "all", "clean all build artifacts");
322 opts.optflag("", "check", "check formatting instead of applying.");
328 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
329 let mut extra_help = String::new();
331 // All subcommands except `clean` can have an optional "Available paths" section
333 let config = Config::parse(&["build".to_string()]);
334 let build = Build::new(config);
336 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
337 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
338 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
340 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
345 println!("{}", opts.usage(subcommand_help));
346 if !extra_help.is_empty() {
347 println!("{}", extra_help);
349 process::exit(exit_code);
352 // Done specifying what options are possible, so do the getopts parsing
353 let matches = opts.parse(args).unwrap_or_else(|e| {
354 // Invalid argument/option format
355 println!("\n{}\n", e);
356 usage(1, &opts, false, &subcommand_help);
359 // Extra sanity check to make sure we didn't hit this crazy corner case:
361 // ./x.py --frobulate clean build
362 // ^-- option ^ ^- actual subcommand
363 // \_ arg to option could be mistaken as subcommand
364 let mut pass_sanity_check = true;
365 match matches.free.get(0) {
366 Some(check_subcommand) => {
367 if check_subcommand != subcommand {
368 pass_sanity_check = false;
372 pass_sanity_check = false;
375 if !pass_sanity_check {
376 println!("{}\n", subcommand_help);
378 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
379 You may need to move some options to after the subcommand.\n"
383 // Extra help text for some commands
384 match subcommand.as_str() {
386 subcommand_help.push_str(
389 This subcommand accepts a number of paths to directories to the crates
390 and/or artifacts to compile. For example:
392 ./x.py build library/core
393 ./x.py build library/core library/proc_macro
394 ./x.py build library/std --stage 1
396 If no arguments are passed then the complete artifacts for that stage are
400 ./x.py build --stage 1
402 For a quick build of a usable compiler, you can pass:
404 ./x.py build --stage 1 library/test
406 This will first build everything once (like `--stage 0` without further
407 arguments would), and then use the compiler built in stage 0 to build
408 library/test and its dependencies.
409 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
413 subcommand_help.push_str(
416 This subcommand accepts a number of paths to directories to the crates
417 and/or artifacts to compile. For example:
419 ./x.py check library/core
420 ./x.py check library/core library/proc_macro
422 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
423 also that since we use `cargo check`, by default this will automatically enable incremental
424 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
425 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
430 subcommand_help.push_str(
433 This subcommand accepts a number of paths to directories to the crates
434 and/or artifacts to run clippy against. For example:
436 ./x.py clippy library/core
437 ./x.py clippy library/core library/proc_macro",
441 subcommand_help.push_str(
444 This subcommand accepts a number of paths to directories to the crates
445 and/or artifacts to run `cargo fix` against. For example:
447 ./x.py fix library/core
448 ./x.py fix library/core library/proc_macro",
452 subcommand_help.push_str(
455 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
456 fails if it is not. For example:
463 subcommand_help.push_str(
466 This subcommand accepts a number of paths to test directories that
467 should be compiled and run. For example:
469 ./x.py test src/test/ui
470 ./x.py test library/std --test-args hash_map
471 ./x.py test library/std --stage 0 --no-doc
472 ./x.py test src/test/ui --bless
473 ./x.py test src/test/ui --compare-mode nll
475 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
476 just like `build library/std --stage N` it tests the compiler produced by the previous
479 Execute tool tests with a tool name argument:
483 If no arguments are passed then the complete artifacts for that stage are
487 ./x.py test --stage 1",
491 subcommand_help.push_str(
494 This subcommand accepts a number of paths to directories of documentation
495 to build. For example:
497 ./x.py doc src/doc/book
498 ./x.py doc src/doc/nomicon
499 ./x.py doc src/doc/book library/std
500 ./x.py doc library/std --open
502 If no arguments are passed then everything is documented:
505 ./x.py doc --stage 1",
509 subcommand_help.push_str(
512 This subcommand accepts a number of paths to tools to build and run. For
515 ./x.py run src/tools/expand-yaml-anchors
517 At least a tool needs to be called.",
521 subcommand_help.push_str(&format!(
523 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
526 This subcommand accepts a 'profile' to use for builds. For example:
530 The profile is optional and you will be prompted interactively if it is not given.
531 The following profiles are available:
534 Profile::all_for_help(" ").trim_end()
539 // Get any optional paths which occur after the subcommand
540 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
542 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
543 let verbose = matches.opt_present("verbose");
545 // User passed in -h/--help?
546 if matches.opt_present("help") {
547 usage(0, &opts, verbose, &subcommand_help);
550 let cmd = match subcommand.as_str() {
551 "build" | "b" => Subcommand::Build { paths },
553 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
555 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
556 "fix" => Subcommand::Fix { paths },
557 "test" | "t" => Subcommand::Test {
559 bless: matches.opt_present("bless"),
560 compare_mode: matches.opt_str("compare-mode"),
561 pass: matches.opt_str("pass"),
562 run: matches.opt_str("run"),
563 test_args: matches.opt_strs("test-args"),
564 rustc_args: matches.opt_strs("rustc-args"),
565 fail_fast: !matches.opt_present("no-fail-fast"),
566 rustfix_coverage: matches.opt_present("rustfix-coverage"),
567 doc_tests: if matches.opt_present("doc") {
569 } else if matches.opt_present("no-doc") {
575 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
576 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
578 if !paths.is_empty() {
579 println!("\nclean does not take a path argument\n");
580 usage(1, &opts, verbose, &subcommand_help);
583 Subcommand::Clean { all: matches.opt_present("all") }
585 "fmt" => Subcommand::Format { check: matches.opt_present("check"), paths },
586 "dist" => Subcommand::Dist { paths },
587 "install" => Subcommand::Install { paths },
589 if paths.is_empty() {
590 println!("\nrun requires at least a path!\n");
591 usage(1, &opts, verbose, &subcommand_help);
593 Subcommand::Run { paths }
596 let profile = if paths.len() > 1 {
597 println!("\nat most one profile can be passed to setup\n");
598 usage(1, &opts, verbose, &subcommand_help)
599 } else if let Some(path) = paths.pop() {
600 let profile_string = t!(path.into_os_string().into_string().map_err(
601 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
604 profile_string.parse().unwrap_or_else(|err| {
605 eprintln!("error: {}", err);
606 eprintln!("help: the available profiles are:");
607 eprint!("{}", Profile::all_for_help("- "));
608 std::process::exit(1);
611 t!(crate::setup::interactive_path())
613 Subcommand::Setup { profile }
616 usage(1, &opts, verbose, &subcommand_help);
620 if let Subcommand::Check { .. } = &cmd {
621 if matches.opt_str("keep-stage").is_some()
622 || matches.opt_str("keep-stage-std").is_some()
624 println!("--keep-stage not yet supported for x.py check");
630 verbose: matches.opt_count("verbose"),
631 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
632 dry_run: matches.opt_present("dry-run"),
633 on_fail: matches.opt_str("on-fail"),
634 rustc_error_format: matches.opt_str("error-format"),
635 json_output: matches.opt_present("json-output"),
637 .opt_strs("keep-stage")
639 .map(|j| j.parse().expect("`keep-stage` should be a number"))
641 keep_stage_std: matches
642 .opt_strs("keep-stage-std")
644 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
646 host: if matches.opt_present("host") {
648 split(&matches.opt_strs("host"))
650 .map(|x| TargetSelection::from_user(&x))
651 .collect::<Vec<_>>(),
656 target: if matches.opt_present("target") {
658 split(&matches.opt_strs("target"))
660 .map(|x| TargetSelection::from_user(&x))
661 .collect::<Vec<_>>(),
667 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
669 incremental: matches.opt_present("incremental"),
670 exclude: split(&matches.opt_strs("exclude"))
673 .collect::<Vec<_>>(),
674 include_default_paths: matches.opt_present("include-default-paths"),
675 deny_warnings: parse_deny_warnings(&matches),
676 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
677 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
680 .opt_get_default("color", Color::Auto)
681 .expect("`color` should be `always`, `never`, or `auto`"),
682 rust_profile_use: matches.opt_str("rust-profile-use"),
683 rust_profile_generate: matches.opt_str("rust-profile-generate"),
689 pub fn test_args(&self) -> Vec<&str> {
691 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
692 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
698 pub fn rustc_args(&self) -> Vec<&str> {
700 Subcommand::Test { ref rustc_args, .. } => {
701 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
707 pub fn fail_fast(&self) -> bool {
709 Subcommand::Test { fail_fast, .. } => fail_fast,
714 pub fn doc_tests(&self) -> DocTests {
716 Subcommand::Test { doc_tests, .. } => doc_tests,
721 pub fn bless(&self) -> bool {
723 Subcommand::Test { bless, .. } => bless,
728 pub fn rustfix_coverage(&self) -> bool {
730 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
735 pub fn compare_mode(&self) -> Option<&str> {
737 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
742 pub fn pass(&self) -> Option<&str> {
744 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
749 pub fn run(&self) -> Option<&str> {
751 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
756 pub fn open(&self) -> bool {
758 Subcommand::Doc { open, .. } => open,
764 fn split(s: &[String]) -> Vec<String> {
765 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
768 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
769 match matches.opt_str("warnings").as_deref() {
770 Some("deny") => Some(true),
771 Some("warn") => Some(false),
773 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);