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>,
78 // Whether to run checking over all targets (e.g., unit / integration
99 /// Whether to automatically update stderr/stdout files
101 compare_mode: Option<String>,
102 pass: Option<String>,
103 test_args: Vec<String>,
104 rustc_args: Vec<String>,
107 rustfix_coverage: bool,
111 test_args: Vec<String>,
130 impl Default for Subcommand {
131 fn default() -> Subcommand {
132 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
137 pub fn parse(args: &[String]) -> Flags {
138 let mut subcommand_help = String::from(
140 Usage: x.py <subcommand> [options] [<paths>...]
143 build, b Compile either the compiler or libraries
144 check, c Compile either the compiler or libraries, using cargo check
145 clippy Run clippy (uses rustup/cargo-installed clippy binary)
148 test, t Build and run some test suites
149 bench Build and run some benchmarks
150 doc Build documentation
151 clean Clean out build directories
152 dist Build distribution artifacts
153 install Install distribution artifacts
154 run, r Run tools contained in this repository
155 setup Create a config.toml (making it easier to use `x.py` itself)
157 To learn more about a subcommand, run `./x.py <subcommand> -h`",
160 let mut opts = Options::new();
161 // Options common to all subcommands
162 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
163 opts.optflag("i", "incremental", "use incremental compilation");
164 opts.optopt("", "config", "TOML configuration file for build", "FILE");
165 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
166 opts.optmulti("", "host", "host targets to build", "HOST");
167 opts.optmulti("", "target", "target targets to build", "TARGET");
168 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
171 "include-default-paths",
172 "include default paths in addition to the provided ones",
174 opts.optopt("", "on-fail", "command to run on failure", "CMD");
175 opts.optflag("", "dry-run", "dry run; don't build anything");
179 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
180 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
186 "stage(s) to keep without recompiling \
187 (pass multiple times to keep e.g., both stages 0 and 1)",
193 "stage(s) of the standard library to keep without recompiling \
194 (pass multiple times to keep e.g., both stages 0 and 1)",
197 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
199 "number of jobs to run in parallel; \
200 defaults to {} (this host's logical CPU count)",
203 opts.optopt("j", "jobs", &j_msg, "JOBS");
204 opts.optflag("h", "help", "print this help message");
208 "if value is deny, will deny warnings, otherwise use default",
211 opts.optopt("", "error-format", "rustc error format", "FORMAT");
212 opts.optflag("", "json-output", "use message-format=json");
213 opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE");
217 "whether rebuilding llvm should be skipped \
218 a VALUE of TRUE indicates that llvm will not be rebuilt \
219 VALUE overrides the skip-rebuild option in config.toml.",
223 // We can't use getopt to parse the options until we have completed specifying which
224 // options are valid, but under the current implementation, some options are conditional on
225 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
226 // complete the definition of the options. Then we can use the getopt::Matches object from
228 let subcommand = args.iter().find(|&s| {
247 let subcommand = match subcommand {
250 // No or an invalid subcommand -- show the general usage and subcommand help
251 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
253 println!("{}\n", subcommand_help);
254 let exit_code = if args.is_empty() { 0 } else { 1 };
255 process::exit(exit_code);
259 // Some subcommands get extra options
260 match subcommand.as_str() {
262 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
266 "extra arguments to be passed for the test tool being used \
267 (e.g. libtest, compiletest or rustdoc)",
273 "extra options to pass the compiler when running tests",
276 opts.optflag("", "no-doc", "do not run doc tests");
277 opts.optflag("", "doc", "only run doc tests");
278 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
282 "mode describing what file the actual ui output will be compared to",
288 "force {check,build,run}-pass tests to this mode.",
289 "check | build | run",
294 "enable this to generate a Rustfix coverage file, which is saved in \
295 `/<build_base>/rustfix_missing_coverage.txt`",
299 opts.optflag("", "all-targets", "Check all targets");
302 opts.optmulti("", "test-args", "extra arguments", "ARGS");
305 opts.optflag("", "fix", "automatically apply lint suggestions");
308 opts.optflag("", "open", "open the docs in a browser");
311 opts.optflag("", "all", "clean all build artifacts");
314 opts.optflag("", "check", "check formatting instead of applying.");
320 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
321 let mut extra_help = String::new();
323 // All subcommands except `clean` can have an optional "Available paths" section
325 let config = Config::parse(&["build".to_string()]);
326 let build = Build::new(config);
328 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
329 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
330 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
332 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
337 println!("{}", opts.usage(subcommand_help));
338 if !extra_help.is_empty() {
339 println!("{}", extra_help);
341 process::exit(exit_code);
344 // Done specifying what options are possible, so do the getopts parsing
345 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
346 // Invalid argument/option format
347 println!("\n{}\n", e);
348 usage(1, &opts, false, &subcommand_help);
351 // Extra sanity check to make sure we didn't hit this crazy corner case:
353 // ./x.py --frobulate clean build
354 // ^-- option ^ ^- actual subcommand
355 // \_ arg to option could be mistaken as subcommand
356 let mut pass_sanity_check = true;
357 match matches.free.get(0) {
358 Some(check_subcommand) => {
359 if check_subcommand != subcommand {
360 pass_sanity_check = false;
364 pass_sanity_check = false;
367 if !pass_sanity_check {
368 println!("{}\n", subcommand_help);
370 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
371 You may need to move some options to after the subcommand.\n"
375 // Extra help text for some commands
376 match subcommand.as_str() {
378 subcommand_help.push_str(
381 This subcommand accepts a number of paths to directories to the crates
382 and/or artifacts to compile. For example:
384 ./x.py build library/core
385 ./x.py build library/core library/proc_macro
386 ./x.py build library/std --stage 1
388 If no arguments are passed then the complete artifacts for that stage are
392 ./x.py build --stage 1
394 For a quick build of a usable compiler, you can pass:
396 ./x.py build --stage 1 library/test
398 This will first build everything once (like `--stage 0` without further
399 arguments would), and then use the compiler built in stage 0 to build
400 library/test and its dependencies.
401 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
405 subcommand_help.push_str(
408 This subcommand accepts a number of paths to directories to the crates
409 and/or artifacts to compile. For example:
411 ./x.py check library/core
412 ./x.py check library/core library/proc_macro
414 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
415 also that since we use `cargo check`, by default this will automatically enable incremental
416 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
417 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
422 subcommand_help.push_str(
425 This subcommand accepts a number of paths to directories to the crates
426 and/or artifacts to run clippy against. For example:
428 ./x.py clippy library/core
429 ./x.py clippy library/core library/proc_macro",
433 subcommand_help.push_str(
436 This subcommand accepts a number of paths to directories to the crates
437 and/or artifacts to run `cargo fix` against. For example:
439 ./x.py fix library/core
440 ./x.py fix library/core library/proc_macro",
444 subcommand_help.push_str(
447 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
448 fails if it is not. For example:
455 subcommand_help.push_str(
458 This subcommand accepts a number of paths to test directories that
459 should be compiled and run. For example:
461 ./x.py test src/test/ui
462 ./x.py test library/std --test-args hash_map
463 ./x.py test library/std --stage 0 --no-doc
464 ./x.py test src/test/ui --bless
465 ./x.py test src/test/ui --compare-mode nll
467 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
468 just like `build library/std --stage N` it tests the compiler produced by the previous
471 Execute tool tests with a tool name argument:
475 If no arguments are passed then the complete artifacts for that stage are
479 ./x.py test --stage 1",
483 subcommand_help.push_str(
486 This subcommand accepts a number of paths to directories of documentation
487 to build. For example:
489 ./x.py doc src/doc/book
490 ./x.py doc src/doc/nomicon
491 ./x.py doc src/doc/book library/std
492 ./x.py doc library/std --open
494 If no arguments are passed then everything is documented:
497 ./x.py doc --stage 1",
501 subcommand_help.push_str(
504 This subcommand accepts a number of paths to tools to build and run. For
507 ./x.py run src/tools/expand-yaml-anchors
509 At least a tool needs to be called.",
513 subcommand_help.push_str(&format!(
515 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
518 This subcommand accepts a 'profile' to use for builds. For example:
522 The profile is optional and you will be prompted interactively if it is not given.
523 The following profiles are available:
526 Profile::all_for_help(" ").trim_end()
531 // Get any optional paths which occur after the subcommand
532 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
534 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
535 let verbose = matches.opt_present("verbose");
537 // User passed in -h/--help?
538 if matches.opt_present("help") {
539 usage(0, &opts, verbose, &subcommand_help);
542 let cmd = match subcommand.as_str() {
543 "build" | "b" => Subcommand::Build { paths },
545 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
547 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
548 "fix" => Subcommand::Fix { paths },
549 "test" | "t" => Subcommand::Test {
551 bless: matches.opt_present("bless"),
552 compare_mode: matches.opt_str("compare-mode"),
553 pass: matches.opt_str("pass"),
554 test_args: matches.opt_strs("test-args"),
555 rustc_args: matches.opt_strs("rustc-args"),
556 fail_fast: !matches.opt_present("no-fail-fast"),
557 rustfix_coverage: matches.opt_present("rustfix-coverage"),
558 doc_tests: if matches.opt_present("doc") {
560 } else if matches.opt_present("no-doc") {
566 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
567 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
569 if !paths.is_empty() {
570 println!("\nclean does not take a path argument\n");
571 usage(1, &opts, verbose, &subcommand_help);
574 Subcommand::Clean { all: matches.opt_present("all") }
576 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
577 "dist" => Subcommand::Dist { paths },
578 "install" => Subcommand::Install { paths },
580 if paths.is_empty() {
581 println!("\nrun requires at least a path!\n");
582 usage(1, &opts, verbose, &subcommand_help);
584 Subcommand::Run { paths }
587 let profile = if paths.len() > 1 {
588 println!("\nat most one profile can be passed to setup\n");
589 usage(1, &opts, verbose, &subcommand_help)
590 } else if let Some(path) = paths.pop() {
591 let profile_string = t!(path.into_os_string().into_string().map_err(
592 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
595 profile_string.parse().unwrap_or_else(|err| {
596 eprintln!("error: {}", err);
597 eprintln!("help: the available profiles are:");
598 eprint!("{}", Profile::all_for_help("- "));
599 std::process::exit(1);
602 t!(crate::setup::interactive_path())
604 Subcommand::Setup { profile }
607 usage(1, &opts, verbose, &subcommand_help);
611 if let Subcommand::Check { .. } = &cmd {
612 if matches.opt_str("stage").is_some() {
613 println!("--stage not supported for x.py check, always treated as stage 0");
616 if matches.opt_str("keep-stage").is_some()
617 || matches.opt_str("keep-stage-std").is_some()
619 println!("--keep-stage not supported for x.py check, only one stage available");
625 verbose: matches.opt_count("verbose"),
626 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
627 dry_run: matches.opt_present("dry-run"),
628 on_fail: matches.opt_str("on-fail"),
629 rustc_error_format: matches.opt_str("error-format"),
630 json_output: matches.opt_present("json-output"),
632 .opt_strs("keep-stage")
634 .map(|j| j.parse().expect("`keep-stage` should be a number"))
636 keep_stage_std: matches
637 .opt_strs("keep-stage-std")
639 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
641 host: if matches.opt_present("host") {
643 split(&matches.opt_strs("host"))
645 .map(|x| TargetSelection::from_user(&x))
646 .collect::<Vec<_>>(),
651 target: if matches.opt_present("target") {
653 split(&matches.opt_strs("target"))
655 .map(|x| TargetSelection::from_user(&x))
656 .collect::<Vec<_>>(),
662 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
664 incremental: matches.opt_present("incremental"),
665 exclude: split(&matches.opt_strs("exclude"))
668 .collect::<Vec<_>>(),
669 include_default_paths: matches.opt_present("include-default-paths"),
670 deny_warnings: parse_deny_warnings(&matches),
671 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
672 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
675 .opt_get_default("color", Color::Auto)
676 .expect("`color` should be `always`, `never`, or `auto`"),
682 pub fn test_args(&self) -> Vec<&str> {
684 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
685 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
691 pub fn rustc_args(&self) -> Vec<&str> {
693 Subcommand::Test { ref rustc_args, .. } => {
694 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
700 pub fn fail_fast(&self) -> bool {
702 Subcommand::Test { fail_fast, .. } => fail_fast,
707 pub fn doc_tests(&self) -> DocTests {
709 Subcommand::Test { doc_tests, .. } => doc_tests,
714 pub fn bless(&self) -> bool {
716 Subcommand::Test { bless, .. } => bless,
721 pub fn rustfix_coverage(&self) -> bool {
723 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
728 pub fn compare_mode(&self) -> Option<&str> {
730 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
735 pub fn pass(&self) -> Option<&str> {
737 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
742 pub fn open(&self) -> bool {
744 Subcommand::Doc { open, .. } => open,
750 fn split(s: &[String]) -> Vec<String> {
751 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
754 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
755 match matches.opt_str("warnings").as_deref() {
756 Some("deny") => Some(true),
757 Some("warn") => Some(false),
759 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);