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>,
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 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", "rustc error format", "FORMAT");
226 opts.optopt("", "rust-profile-use", "rustc error format", "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| {
252 let subcommand = match subcommand {
255 // No or an invalid subcommand -- show the general usage and subcommand help
256 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
258 println!("{}\n", subcommand_help);
259 let exit_code = if args.is_empty() { 0 } else { 1 };
260 process::exit(exit_code);
264 // Some subcommands get extra options
265 match subcommand.as_str() {
267 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
271 "extra arguments to be passed for the test tool being used \
272 (e.g. libtest, compiletest or rustdoc)",
278 "extra options to pass the compiler when running tests",
281 opts.optflag("", "no-doc", "do not run doc tests");
282 opts.optflag("", "doc", "only run doc tests");
283 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
287 "mode describing what file the actual ui output will be compared to",
293 "force {check,build,run}-pass tests to this mode.",
294 "check | build | run",
299 "enable this to generate a Rustfix coverage file, which is saved in \
300 `/<build_base>/rustfix_missing_coverage.txt`",
304 opts.optflag("", "all-targets", "Check all targets");
307 opts.optmulti("", "test-args", "extra arguments", "ARGS");
310 opts.optflag("", "fix", "automatically apply lint suggestions");
313 opts.optflag("", "open", "open the docs in a browser");
316 opts.optflag("", "all", "clean all build artifacts");
319 opts.optflag("", "check", "check formatting instead of applying.");
325 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
326 let mut extra_help = String::new();
328 // All subcommands except `clean` can have an optional "Available paths" section
330 let config = Config::parse(&["build".to_string()]);
331 let build = Build::new(config);
333 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
334 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
335 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
337 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
342 println!("{}", opts.usage(subcommand_help));
343 if !extra_help.is_empty() {
344 println!("{}", extra_help);
346 process::exit(exit_code);
349 // Done specifying what options are possible, so do the getopts parsing
350 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
351 // Invalid argument/option format
352 println!("\n{}\n", e);
353 usage(1, &opts, false, &subcommand_help);
356 // Extra sanity check to make sure we didn't hit this crazy corner case:
358 // ./x.py --frobulate clean build
359 // ^-- option ^ ^- actual subcommand
360 // \_ arg to option could be mistaken as subcommand
361 let mut pass_sanity_check = true;
362 match matches.free.get(0) {
363 Some(check_subcommand) => {
364 if check_subcommand != subcommand {
365 pass_sanity_check = false;
369 pass_sanity_check = false;
372 if !pass_sanity_check {
373 println!("{}\n", subcommand_help);
375 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
376 You may need to move some options to after the subcommand.\n"
380 // Extra help text for some commands
381 match subcommand.as_str() {
383 subcommand_help.push_str(
386 This subcommand accepts a number of paths to directories to the crates
387 and/or artifacts to compile. For example:
389 ./x.py build library/core
390 ./x.py build library/core library/proc_macro
391 ./x.py build library/std --stage 1
393 If no arguments are passed then the complete artifacts for that stage are
397 ./x.py build --stage 1
399 For a quick build of a usable compiler, you can pass:
401 ./x.py build --stage 1 library/test
403 This will first build everything once (like `--stage 0` without further
404 arguments would), and then use the compiler built in stage 0 to build
405 library/test and its dependencies.
406 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
410 subcommand_help.push_str(
413 This subcommand accepts a number of paths to directories to the crates
414 and/or artifacts to compile. For example:
416 ./x.py check library/core
417 ./x.py check library/core library/proc_macro
419 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
420 also that since we use `cargo check`, by default this will automatically enable incremental
421 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
422 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
427 subcommand_help.push_str(
430 This subcommand accepts a number of paths to directories to the crates
431 and/or artifacts to run clippy against. For example:
433 ./x.py clippy library/core
434 ./x.py clippy library/core library/proc_macro",
438 subcommand_help.push_str(
441 This subcommand accepts a number of paths to directories to the crates
442 and/or artifacts to run `cargo fix` against. For example:
444 ./x.py fix library/core
445 ./x.py fix library/core library/proc_macro",
449 subcommand_help.push_str(
452 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
453 fails if it is not. For example:
460 subcommand_help.push_str(
463 This subcommand accepts a number of paths to test directories that
464 should be compiled and run. For example:
466 ./x.py test src/test/ui
467 ./x.py test library/std --test-args hash_map
468 ./x.py test library/std --stage 0 --no-doc
469 ./x.py test src/test/ui --bless
470 ./x.py test src/test/ui --compare-mode nll
472 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
473 just like `build library/std --stage N` it tests the compiler produced by the previous
476 Execute tool tests with a tool name argument:
480 If no arguments are passed then the complete artifacts for that stage are
484 ./x.py test --stage 1",
488 subcommand_help.push_str(
491 This subcommand accepts a number of paths to directories of documentation
492 to build. For example:
494 ./x.py doc src/doc/book
495 ./x.py doc src/doc/nomicon
496 ./x.py doc src/doc/book library/std
497 ./x.py doc library/std --open
499 If no arguments are passed then everything is documented:
502 ./x.py doc --stage 1",
506 subcommand_help.push_str(
509 This subcommand accepts a number of paths to tools to build and run. For
512 ./x.py run src/tools/expand-yaml-anchors
514 At least a tool needs to be called.",
518 subcommand_help.push_str(&format!(
520 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
523 This subcommand accepts a 'profile' to use for builds. For example:
527 The profile is optional and you will be prompted interactively if it is not given.
528 The following profiles are available:
531 Profile::all_for_help(" ").trim_end()
536 // Get any optional paths which occur after the subcommand
537 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
539 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
540 let verbose = matches.opt_present("verbose");
542 // User passed in -h/--help?
543 if matches.opt_present("help") {
544 usage(0, &opts, verbose, &subcommand_help);
547 let cmd = match subcommand.as_str() {
548 "build" | "b" => Subcommand::Build { paths },
550 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
552 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
553 "fix" => Subcommand::Fix { paths },
554 "test" | "t" => Subcommand::Test {
556 bless: matches.opt_present("bless"),
557 compare_mode: matches.opt_str("compare-mode"),
558 pass: matches.opt_str("pass"),
559 test_args: matches.opt_strs("test-args"),
560 rustc_args: matches.opt_strs("rustc-args"),
561 fail_fast: !matches.opt_present("no-fail-fast"),
562 rustfix_coverage: matches.opt_present("rustfix-coverage"),
563 doc_tests: if matches.opt_present("doc") {
565 } else if matches.opt_present("no-doc") {
571 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
572 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
574 if !paths.is_empty() {
575 println!("\nclean does not take a path argument\n");
576 usage(1, &opts, verbose, &subcommand_help);
579 Subcommand::Clean { all: matches.opt_present("all") }
581 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
582 "dist" => Subcommand::Dist { paths },
583 "install" => Subcommand::Install { paths },
585 if paths.is_empty() {
586 println!("\nrun requires at least a path!\n");
587 usage(1, &opts, verbose, &subcommand_help);
589 Subcommand::Run { paths }
592 let profile = if paths.len() > 1 {
593 println!("\nat most one profile can be passed to setup\n");
594 usage(1, &opts, verbose, &subcommand_help)
595 } else if let Some(path) = paths.pop() {
596 let profile_string = t!(path.into_os_string().into_string().map_err(
597 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
600 profile_string.parse().unwrap_or_else(|err| {
601 eprintln!("error: {}", err);
602 eprintln!("help: the available profiles are:");
603 eprint!("{}", Profile::all_for_help("- "));
604 std::process::exit(1);
607 t!(crate::setup::interactive_path())
609 Subcommand::Setup { profile }
612 usage(1, &opts, verbose, &subcommand_help);
616 if let Subcommand::Check { .. } = &cmd {
617 if matches.opt_str("keep-stage").is_some()
618 || matches.opt_str("keep-stage-std").is_some()
620 println!("--keep-stage not yet supported for x.py check");
626 verbose: matches.opt_count("verbose"),
627 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
628 dry_run: matches.opt_present("dry-run"),
629 on_fail: matches.opt_str("on-fail"),
630 rustc_error_format: matches.opt_str("error-format"),
631 json_output: matches.opt_present("json-output"),
633 .opt_strs("keep-stage")
635 .map(|j| j.parse().expect("`keep-stage` should be a number"))
637 keep_stage_std: matches
638 .opt_strs("keep-stage-std")
640 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
642 host: if matches.opt_present("host") {
644 split(&matches.opt_strs("host"))
646 .map(|x| TargetSelection::from_user(&x))
647 .collect::<Vec<_>>(),
652 target: if matches.opt_present("target") {
654 split(&matches.opt_strs("target"))
656 .map(|x| TargetSelection::from_user(&x))
657 .collect::<Vec<_>>(),
663 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
665 incremental: matches.opt_present("incremental"),
666 exclude: split(&matches.opt_strs("exclude"))
669 .collect::<Vec<_>>(),
670 include_default_paths: matches.opt_present("include-default-paths"),
671 deny_warnings: parse_deny_warnings(&matches),
672 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
673 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
676 .opt_get_default("color", Color::Auto)
677 .expect("`color` should be `always`, `never`, or `auto`"),
678 rust_profile_use: matches.opt_str("rust-profile-use"),
679 rust_profile_generate: matches.opt_str("rust-profile-generate"),
685 pub fn test_args(&self) -> Vec<&str> {
687 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
688 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
694 pub fn rustc_args(&self) -> Vec<&str> {
696 Subcommand::Test { ref rustc_args, .. } => {
697 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
703 pub fn fail_fast(&self) -> bool {
705 Subcommand::Test { fail_fast, .. } => fail_fast,
710 pub fn doc_tests(&self) -> DocTests {
712 Subcommand::Test { doc_tests, .. } => doc_tests,
717 pub fn bless(&self) -> bool {
719 Subcommand::Test { bless, .. } => bless,
724 pub fn rustfix_coverage(&self) -> bool {
726 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
731 pub fn compare_mode(&self) -> Option<&str> {
733 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
738 pub fn pass(&self) -> Option<&str> {
740 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
745 pub fn open(&self) -> bool {
747 Subcommand::Doc { open, .. } => open,
753 fn split(s: &[String]) -> Vec<String> {
754 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
757 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
758 match matches.opt_str("warnings").as_deref() {
759 Some("deny") => Some(true),
760 Some("warn") => Some(false),
762 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);