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>,
100 /// Whether to automatically update stderr/stdout files
103 compare_mode: Option<String>,
104 pass: Option<String>,
106 test_args: Vec<String>,
107 rustc_args: Vec<String>,
110 rustfix_coverage: bool,
114 test_args: Vec<String>,
133 impl Default for Subcommand {
134 fn default() -> Subcommand {
135 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
140 pub fn parse(args: &[String]) -> Flags {
141 let mut subcommand_help = String::from(
143 Usage: x.py <subcommand> [options] [<paths>...]
146 build, b Compile either the compiler or libraries
147 check, c Compile either the compiler or libraries, using cargo check
148 clippy Run clippy (uses rustup/cargo-installed clippy binary)
151 test, t Build and run some test suites
152 bench Build and run some benchmarks
153 doc, d Build documentation
154 clean Clean out build directories
155 dist Build distribution artifacts
156 install Install distribution artifacts
157 run, r Run tools contained in this repository
158 setup Create a config.toml (making it easier to use `x.py` itself)
160 To learn more about a subcommand, run `./x.py <subcommand> -h`",
163 let mut opts = Options::new();
164 // Options common to all subcommands
165 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
166 opts.optflag("i", "incremental", "use incremental compilation");
167 opts.optopt("", "config", "TOML configuration file for build", "FILE");
168 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
169 opts.optmulti("", "host", "host targets to build", "HOST");
170 opts.optmulti("", "target", "target targets to build", "TARGET");
171 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
174 "include-default-paths",
175 "include default paths in addition to the provided ones",
177 opts.optopt("", "on-fail", "command to run on failure", "CMD");
178 opts.optflag("", "dry-run", "dry run; don't build anything");
182 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
183 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
189 "stage(s) to keep without recompiling \
190 (pass multiple times to keep e.g., both stages 0 and 1)",
196 "stage(s) of the standard library to keep without recompiling \
197 (pass multiple times to keep e.g., both stages 0 and 1)",
200 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
202 "number of jobs to run in parallel; \
203 defaults to {} (this host's logical CPU count)",
206 opts.optopt("j", "jobs", &j_msg, "JOBS");
207 opts.optflag("h", "help", "print this help message");
211 "if value is deny, will deny warnings, otherwise use default",
214 opts.optopt("", "error-format", "rustc error format", "FORMAT");
215 opts.optflag("", "json-output", "use message-format=json");
216 opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE");
220 "whether rebuilding llvm should be skipped \
221 a VALUE of TRUE indicates that llvm will not be rebuilt \
222 VALUE overrides the skip-rebuild option in config.toml.",
225 opts.optopt("", "rust-profile-generate", "generate PGO profile with rustc build", "FORMAT");
226 opts.optopt("", "rust-profile-use", "use PGO profile for rustc build", "FORMAT");
228 // We can't use getopt to parse the options until we have completed specifying which
229 // options are valid, but under the current implementation, some options are conditional on
230 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
231 // complete the definition of the options. Then we can use the getopt::Matches object from
233 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");
285 opts.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged");
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 if matches.opt_present("all-targets") {
555 "Warning: --all-targets is now on by default and does not need to be passed explicitly."
558 Subcommand::Check { paths }
560 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
561 "fix" => Subcommand::Fix { paths },
562 "test" | "t" => Subcommand::Test {
564 bless: matches.opt_present("bless"),
565 force_rerun: matches.opt_present("force-rerun"),
566 compare_mode: matches.opt_str("compare-mode"),
567 pass: matches.opt_str("pass"),
568 run: matches.opt_str("run"),
569 test_args: matches.opt_strs("test-args"),
570 rustc_args: matches.opt_strs("rustc-args"),
571 fail_fast: !matches.opt_present("no-fail-fast"),
572 rustfix_coverage: matches.opt_present("rustfix-coverage"),
573 doc_tests: if matches.opt_present("doc") {
575 } else if matches.opt_present("no-doc") {
581 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
582 "doc" | "d" => Subcommand::Doc { paths, open: matches.opt_present("open") },
584 if !paths.is_empty() {
585 println!("\nclean does not take a path argument\n");
586 usage(1, &opts, verbose, &subcommand_help);
589 Subcommand::Clean { all: matches.opt_present("all") }
591 "fmt" => Subcommand::Format { check: matches.opt_present("check"), paths },
592 "dist" => Subcommand::Dist { paths },
593 "install" => Subcommand::Install { paths },
595 if paths.is_empty() {
596 println!("\nrun requires at least a path!\n");
597 usage(1, &opts, verbose, &subcommand_help);
599 Subcommand::Run { paths }
602 let profile = if paths.len() > 1 {
603 println!("\nat most one profile can be passed to setup\n");
604 usage(1, &opts, verbose, &subcommand_help)
605 } else if let Some(path) = paths.pop() {
606 let profile_string = t!(path.into_os_string().into_string().map_err(
607 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
610 profile_string.parse().unwrap_or_else(|err| {
611 eprintln!("error: {}", err);
612 eprintln!("help: the available profiles are:");
613 eprint!("{}", Profile::all_for_help("- "));
614 std::process::exit(1);
617 t!(crate::setup::interactive_path())
619 Subcommand::Setup { profile }
622 usage(1, &opts, verbose, &subcommand_help);
626 if let Subcommand::Check { .. } = &cmd {
627 if matches.opt_str("keep-stage").is_some()
628 || matches.opt_str("keep-stage-std").is_some()
630 println!("--keep-stage not yet supported for x.py check");
636 verbose: matches.opt_count("verbose"),
637 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
638 dry_run: matches.opt_present("dry-run"),
639 on_fail: matches.opt_str("on-fail"),
640 rustc_error_format: matches.opt_str("error-format"),
641 json_output: matches.opt_present("json-output"),
643 .opt_strs("keep-stage")
645 .map(|j| j.parse().expect("`keep-stage` should be a number"))
647 keep_stage_std: matches
648 .opt_strs("keep-stage-std")
650 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
652 host: if matches.opt_present("host") {
654 split(&matches.opt_strs("host"))
656 .map(|x| TargetSelection::from_user(&x))
657 .collect::<Vec<_>>(),
662 target: if matches.opt_present("target") {
664 split(&matches.opt_strs("target"))
666 .map(|x| TargetSelection::from_user(&x))
667 .collect::<Vec<_>>(),
673 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
675 incremental: matches.opt_present("incremental"),
676 exclude: split(&matches.opt_strs("exclude"))
679 .collect::<Vec<_>>(),
680 include_default_paths: matches.opt_present("include-default-paths"),
681 deny_warnings: parse_deny_warnings(&matches),
682 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
683 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
686 .opt_get_default("color", Color::Auto)
687 .expect("`color` should be `always`, `never`, or `auto`"),
688 rust_profile_use: matches.opt_str("rust-profile-use"),
689 rust_profile_generate: matches.opt_str("rust-profile-generate"),
695 pub fn test_args(&self) -> Vec<&str> {
697 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
698 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
704 pub fn rustc_args(&self) -> Vec<&str> {
706 Subcommand::Test { ref rustc_args, .. } => {
707 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
713 pub fn fail_fast(&self) -> bool {
715 Subcommand::Test { fail_fast, .. } => fail_fast,
720 pub fn doc_tests(&self) -> DocTests {
722 Subcommand::Test { doc_tests, .. } => doc_tests,
727 pub fn bless(&self) -> bool {
729 Subcommand::Test { bless, .. } => bless,
734 pub fn force_rerun(&self) -> bool {
736 Subcommand::Test { force_rerun, .. } => force_rerun,
741 pub fn rustfix_coverage(&self) -> bool {
743 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
748 pub fn compare_mode(&self) -> Option<&str> {
750 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
755 pub fn pass(&self) -> Option<&str> {
757 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
762 pub fn run(&self) -> Option<&str> {
764 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
769 pub fn open(&self) -> bool {
771 Subcommand::Doc { open, .. } => open,
777 fn split(s: &[String]) -> Vec<String> {
778 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
781 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
782 match matches.opt_str("warnings").as_deref() {
783 Some("deny") => Some(true),
784 Some("warn") => Some(false),
786 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);