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
102 /// Whether to automatically update stderr/stdout files
104 compare_mode: Option<String>,
105 pass: Option<String>,
107 test_args: Vec<String>,
108 rustc_args: Vec<String>,
111 rustfix_coverage: bool,
115 test_args: Vec<String>,
134 impl Default for Subcommand {
135 fn default() -> Subcommand {
136 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
141 pub fn parse(args: &[String]) -> Flags {
142 let mut subcommand_help = String::from(
144 Usage: x.py <subcommand> [options] [<paths>...]
147 build, b Compile either the compiler or libraries
148 check, c Compile either the compiler or libraries, using cargo check
149 clippy Run clippy (uses rustup/cargo-installed clippy binary)
152 test, t Build and run some test suites
153 bench Build and run some benchmarks
154 doc Build documentation
155 clean Clean out build directories
156 dist Build distribution artifacts
157 install Install distribution artifacts
158 run, r Run tools contained in this repository
159 setup Create a config.toml (making it easier to use `x.py` itself)
161 To learn more about a subcommand, run `./x.py <subcommand> -h`",
164 let mut opts = Options::new();
165 // Options common to all subcommands
166 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
167 opts.optflag("i", "incremental", "use incremental compilation");
168 opts.optopt("", "config", "TOML configuration file for build", "FILE");
169 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
170 opts.optmulti("", "host", "host targets to build", "HOST");
171 opts.optmulti("", "target", "target targets to build", "TARGET");
172 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
175 "include-default-paths",
176 "include default paths in addition to the provided ones",
178 opts.optopt("", "on-fail", "command to run on failure", "CMD");
179 opts.optflag("", "dry-run", "dry run; don't build anything");
183 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
184 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
190 "stage(s) to keep without recompiling \
191 (pass multiple times to keep e.g., both stages 0 and 1)",
197 "stage(s) of the standard library to keep without recompiling \
198 (pass multiple times to keep e.g., both stages 0 and 1)",
201 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
203 "number of jobs to run in parallel; \
204 defaults to {} (this host's logical CPU count)",
207 opts.optopt("j", "jobs", &j_msg, "JOBS");
208 opts.optflag("h", "help", "print this help message");
212 "if value is deny, will deny warnings, otherwise use default",
215 opts.optopt("", "error-format", "rustc error format", "FORMAT");
216 opts.optflag("", "json-output", "use message-format=json");
217 opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE");
221 "whether rebuilding llvm should be skipped \
222 a VALUE of TRUE indicates that llvm will not be rebuilt \
223 VALUE overrides the skip-rebuild option in config.toml.",
226 opts.optopt("", "rust-profile-generate", "generate PGO profile with rustc build", "FORMAT");
227 opts.optopt("", "rust-profile-use", "use PGO profile for rustc build", "FORMAT");
229 // We can't use getopt to parse the options until we have completed specifying which
230 // options are valid, but under the current implementation, some options are conditional on
231 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
232 // complete the definition of the options. Then we can use the getopt::Matches object from
234 let subcommand = args.iter().find(|&s| {
253 let subcommand = match subcommand {
256 // No or an invalid subcommand -- show the general usage and subcommand help
257 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
259 println!("{}\n", subcommand_help);
260 let exit_code = if args.is_empty() { 0 } else { 1 };
261 process::exit(exit_code);
265 // Some subcommands get extra options
266 match subcommand.as_str() {
268 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
272 "extra arguments to be passed for the test tool being used \
273 (e.g. libtest, compiletest or rustdoc)",
279 "extra options to pass the compiler when running tests",
282 opts.optflag("", "no-doc", "do not run doc tests");
283 opts.optflag("", "doc", "only run doc tests");
284 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
288 "mode describing what file the actual ui output will be compared to",
294 "force {check,build,run}-pass tests to this mode.",
295 "check | build | run",
297 opts.optopt("", "run", "whether to execute run-* tests", "auto | always | never");
301 "enable this to generate a Rustfix coverage file, which is saved in \
302 `/<build_base>/rustfix_missing_coverage.txt`",
306 opts.optflag("", "all-targets", "Check all targets");
309 opts.optmulti("", "test-args", "extra arguments", "ARGS");
312 opts.optflag("", "fix", "automatically apply lint suggestions");
315 opts.optflag("", "open", "open the docs in a browser");
318 opts.optflag("", "all", "clean all build artifacts");
321 opts.optflag("", "check", "check formatting instead of applying.");
327 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
328 let mut extra_help = String::new();
330 // All subcommands except `clean` can have an optional "Available paths" section
332 let config = Config::parse(&["build".to_string()]);
333 let build = Build::new(config);
335 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
336 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
337 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
339 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
344 println!("{}", opts.usage(subcommand_help));
345 if !extra_help.is_empty() {
346 println!("{}", extra_help);
348 process::exit(exit_code);
351 // Done specifying what options are possible, so do the getopts parsing
352 let matches = opts.parse(args).unwrap_or_else(|e| {
353 // Invalid argument/option format
354 println!("\n{}\n", e);
355 usage(1, &opts, false, &subcommand_help);
358 // Extra sanity check to make sure we didn't hit this crazy corner case:
360 // ./x.py --frobulate clean build
361 // ^-- option ^ ^- actual subcommand
362 // \_ arg to option could be mistaken as subcommand
363 let mut pass_sanity_check = true;
364 match matches.free.get(0) {
365 Some(check_subcommand) => {
366 if check_subcommand != subcommand {
367 pass_sanity_check = false;
371 pass_sanity_check = false;
374 if !pass_sanity_check {
375 println!("{}\n", subcommand_help);
377 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
378 You may need to move some options to after the subcommand.\n"
382 // Extra help text for some commands
383 match subcommand.as_str() {
385 subcommand_help.push_str(
388 This subcommand accepts a number of paths to directories to the crates
389 and/or artifacts to compile. For example:
391 ./x.py build library/core
392 ./x.py build library/core library/proc_macro
393 ./x.py build library/std --stage 1
395 If no arguments are passed then the complete artifacts for that stage are
399 ./x.py build --stage 1
401 For a quick build of a usable compiler, you can pass:
403 ./x.py build --stage 1 library/test
405 This will first build everything once (like `--stage 0` without further
406 arguments would), and then use the compiler built in stage 0 to build
407 library/test and its dependencies.
408 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
412 subcommand_help.push_str(
415 This subcommand accepts a number of paths to directories to the crates
416 and/or artifacts to compile. For example:
418 ./x.py check library/core
419 ./x.py check library/core library/proc_macro
421 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
422 also that since we use `cargo check`, by default this will automatically enable incremental
423 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
424 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
429 subcommand_help.push_str(
432 This subcommand accepts a number of paths to directories to the crates
433 and/or artifacts to run clippy against. For example:
435 ./x.py clippy library/core
436 ./x.py clippy library/core library/proc_macro",
440 subcommand_help.push_str(
443 This subcommand accepts a number of paths to directories to the crates
444 and/or artifacts to run `cargo fix` against. For example:
446 ./x.py fix library/core
447 ./x.py fix library/core library/proc_macro",
451 subcommand_help.push_str(
454 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
455 fails if it is not. For example:
462 subcommand_help.push_str(
465 This subcommand accepts a number of paths to test directories that
466 should be compiled and run. For example:
468 ./x.py test src/test/ui
469 ./x.py test library/std --test-args hash_map
470 ./x.py test library/std --stage 0 --no-doc
471 ./x.py test src/test/ui --bless
472 ./x.py test src/test/ui --compare-mode nll
474 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
475 just like `build library/std --stage N` it tests the compiler produced by the previous
478 Execute tool tests with a tool name argument:
482 If no arguments are passed then the complete artifacts for that stage are
486 ./x.py test --stage 1",
490 subcommand_help.push_str(
493 This subcommand accepts a number of paths to directories of documentation
494 to build. For example:
496 ./x.py doc src/doc/book
497 ./x.py doc src/doc/nomicon
498 ./x.py doc src/doc/book library/std
499 ./x.py doc library/std --open
501 If no arguments are passed then everything is documented:
504 ./x.py doc --stage 1",
508 subcommand_help.push_str(
511 This subcommand accepts a number of paths to tools to build and run. For
514 ./x.py run src/tools/expand-yaml-anchors
516 At least a tool needs to be called.",
520 subcommand_help.push_str(&format!(
522 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
525 This subcommand accepts a 'profile' to use for builds. For example:
529 The profile is optional and you will be prompted interactively if it is not given.
530 The following profiles are available:
533 Profile::all_for_help(" ").trim_end()
538 // Get any optional paths which occur after the subcommand
539 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
541 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
542 let verbose = matches.opt_present("verbose");
544 // User passed in -h/--help?
545 if matches.opt_present("help") {
546 usage(0, &opts, verbose, &subcommand_help);
549 let cmd = match subcommand.as_str() {
550 "build" | "b" => Subcommand::Build { paths },
552 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
554 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
555 "fix" => Subcommand::Fix { paths },
556 "test" | "t" => Subcommand::Test {
558 bless: matches.opt_present("bless"),
559 compare_mode: matches.opt_str("compare-mode"),
560 pass: matches.opt_str("pass"),
561 run: matches.opt_str("run"),
562 test_args: matches.opt_strs("test-args"),
563 rustc_args: matches.opt_strs("rustc-args"),
564 fail_fast: !matches.opt_present("no-fail-fast"),
565 rustfix_coverage: matches.opt_present("rustfix-coverage"),
566 doc_tests: if matches.opt_present("doc") {
568 } else if matches.opt_present("no-doc") {
574 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
575 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
577 if !paths.is_empty() {
578 println!("\nclean does not take a path argument\n");
579 usage(1, &opts, verbose, &subcommand_help);
582 Subcommand::Clean { all: matches.opt_present("all") }
584 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
585 "dist" => Subcommand::Dist { paths },
586 "install" => Subcommand::Install { paths },
588 if paths.is_empty() {
589 println!("\nrun requires at least a path!\n");
590 usage(1, &opts, verbose, &subcommand_help);
592 Subcommand::Run { paths }
595 let profile = if paths.len() > 1 {
596 println!("\nat most one profile can be passed to setup\n");
597 usage(1, &opts, verbose, &subcommand_help)
598 } else if let Some(path) = paths.pop() {
599 let profile_string = t!(path.into_os_string().into_string().map_err(
600 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
603 profile_string.parse().unwrap_or_else(|err| {
604 eprintln!("error: {}", err);
605 eprintln!("help: the available profiles are:");
606 eprint!("{}", Profile::all_for_help("- "));
607 std::process::exit(1);
610 t!(crate::setup::interactive_path())
612 Subcommand::Setup { profile }
615 usage(1, &opts, verbose, &subcommand_help);
619 if let Subcommand::Check { .. } = &cmd {
620 if matches.opt_str("keep-stage").is_some()
621 || matches.opt_str("keep-stage-std").is_some()
623 println!("--keep-stage not yet supported for x.py check");
629 verbose: matches.opt_count("verbose"),
630 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
631 dry_run: matches.opt_present("dry-run"),
632 on_fail: matches.opt_str("on-fail"),
633 rustc_error_format: matches.opt_str("error-format"),
634 json_output: matches.opt_present("json-output"),
636 .opt_strs("keep-stage")
638 .map(|j| j.parse().expect("`keep-stage` should be a number"))
640 keep_stage_std: matches
641 .opt_strs("keep-stage-std")
643 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
645 host: if matches.opt_present("host") {
647 split(&matches.opt_strs("host"))
649 .map(|x| TargetSelection::from_user(&x))
650 .collect::<Vec<_>>(),
655 target: if matches.opt_present("target") {
657 split(&matches.opt_strs("target"))
659 .map(|x| TargetSelection::from_user(&x))
660 .collect::<Vec<_>>(),
666 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
668 incremental: matches.opt_present("incremental"),
669 exclude: split(&matches.opt_strs("exclude"))
672 .collect::<Vec<_>>(),
673 include_default_paths: matches.opt_present("include-default-paths"),
674 deny_warnings: parse_deny_warnings(&matches),
675 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
676 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
679 .opt_get_default("color", Color::Auto)
680 .expect("`color` should be `always`, `never`, or `auto`"),
681 rust_profile_use: matches.opt_str("rust-profile-use"),
682 rust_profile_generate: matches.opt_str("rust-profile-generate"),
688 pub fn test_args(&self) -> Vec<&str> {
690 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
691 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
697 pub fn rustc_args(&self) -> Vec<&str> {
699 Subcommand::Test { ref rustc_args, .. } => {
700 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
706 pub fn fail_fast(&self) -> bool {
708 Subcommand::Test { fail_fast, .. } => fail_fast,
713 pub fn doc_tests(&self) -> DocTests {
715 Subcommand::Test { doc_tests, .. } => doc_tests,
720 pub fn bless(&self) -> bool {
722 Subcommand::Test { bless, .. } => bless,
727 pub fn rustfix_coverage(&self) -> bool {
729 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
734 pub fn compare_mode(&self) -> Option<&str> {
736 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
741 pub fn pass(&self) -> Option<&str> {
743 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
748 pub fn run(&self) -> Option<&str> {
750 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
755 pub fn open(&self) -> bool {
757 Subcommand::Doc { open, .. } => open,
763 fn split(s: &[String]) -> Vec<String> {
764 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
767 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
768 match matches.opt_str("warnings").as_deref() {
769 Some("deny") => Some(true),
770 Some("warn") => Some(false),
772 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);