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, d 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| {
255 let subcommand = match subcommand {
258 // No or an invalid subcommand -- show the general usage and subcommand help
259 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
261 println!("{}\n", subcommand_help);
262 let exit_code = if args.is_empty() { 0 } else { 1 };
263 process::exit(exit_code);
267 // Some subcommands get extra options
268 match subcommand.as_str() {
270 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
274 "extra arguments to be passed for the test tool being used \
275 (e.g. libtest, compiletest or rustdoc)",
281 "extra options to pass the compiler when running tests",
284 opts.optflag("", "no-doc", "do not run doc tests");
285 opts.optflag("", "doc", "only run doc tests");
286 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
290 "mode describing what file the actual ui output will be compared to",
296 "force {check,build,run}-pass tests to this mode.",
297 "check | build | run",
299 opts.optopt("", "run", "whether to execute run-* tests", "auto | always | never");
303 "enable this to generate a Rustfix coverage file, which is saved in \
304 `/<build_base>/rustfix_missing_coverage.txt`",
308 opts.optflag("", "all-targets", "Check all targets");
311 opts.optmulti("", "test-args", "extra arguments", "ARGS");
314 opts.optflag("", "fix", "automatically apply lint suggestions");
317 opts.optflag("", "open", "open the docs in a browser");
320 opts.optflag("", "all", "clean all build artifacts");
323 opts.optflag("", "check", "check formatting instead of applying.");
329 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
330 let mut extra_help = String::new();
332 // All subcommands except `clean` can have an optional "Available paths" section
334 let config = Config::parse(&["build".to_string()]);
335 let build = Build::new(config);
337 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
338 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
339 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
341 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
346 println!("{}", opts.usage(subcommand_help));
347 if !extra_help.is_empty() {
348 println!("{}", extra_help);
350 process::exit(exit_code);
353 // Done specifying what options are possible, so do the getopts parsing
354 let matches = opts.parse(args).unwrap_or_else(|e| {
355 // Invalid argument/option format
356 println!("\n{}\n", e);
357 usage(1, &opts, false, &subcommand_help);
360 // Extra sanity check to make sure we didn't hit this crazy corner case:
362 // ./x.py --frobulate clean build
363 // ^-- option ^ ^- actual subcommand
364 // \_ arg to option could be mistaken as subcommand
365 let mut pass_sanity_check = true;
366 match matches.free.get(0) {
367 Some(check_subcommand) => {
368 if check_subcommand != subcommand {
369 pass_sanity_check = false;
373 pass_sanity_check = false;
376 if !pass_sanity_check {
377 println!("{}\n", subcommand_help);
379 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
380 You may need to move some options to after the subcommand.\n"
384 // Extra help text for some commands
385 match subcommand.as_str() {
387 subcommand_help.push_str(
390 This subcommand accepts a number of paths to directories to the crates
391 and/or artifacts to compile. For example:
393 ./x.py build library/core
394 ./x.py build library/core library/proc_macro
395 ./x.py build library/std --stage 1
397 If no arguments are passed then the complete artifacts for that stage are
401 ./x.py build --stage 1
403 For a quick build of a usable compiler, you can pass:
405 ./x.py build --stage 1 library/test
407 This will first build everything once (like `--stage 0` without further
408 arguments would), and then use the compiler built in stage 0 to build
409 library/test and its dependencies.
410 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
414 subcommand_help.push_str(
417 This subcommand accepts a number of paths to directories to the crates
418 and/or artifacts to compile. For example:
420 ./x.py check library/core
421 ./x.py check library/core library/proc_macro
423 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
424 also that since we use `cargo check`, by default this will automatically enable incremental
425 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
426 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
431 subcommand_help.push_str(
434 This subcommand accepts a number of paths to directories to the crates
435 and/or artifacts to run clippy against. For example:
437 ./x.py clippy library/core
438 ./x.py clippy library/core library/proc_macro",
442 subcommand_help.push_str(
445 This subcommand accepts a number of paths to directories to the crates
446 and/or artifacts to run `cargo fix` against. For example:
448 ./x.py fix library/core
449 ./x.py fix library/core library/proc_macro",
453 subcommand_help.push_str(
456 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
457 fails if it is not. For example:
464 subcommand_help.push_str(
467 This subcommand accepts a number of paths to test directories that
468 should be compiled and run. For example:
470 ./x.py test src/test/ui
471 ./x.py test library/std --test-args hash_map
472 ./x.py test library/std --stage 0 --no-doc
473 ./x.py test src/test/ui --bless
474 ./x.py test src/test/ui --compare-mode nll
476 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
477 just like `build library/std --stage N` it tests the compiler produced by the previous
480 Execute tool tests with a tool name argument:
484 If no arguments are passed then the complete artifacts for that stage are
488 ./x.py test --stage 1",
492 subcommand_help.push_str(
495 This subcommand accepts a number of paths to directories of documentation
496 to build. For example:
498 ./x.py doc src/doc/book
499 ./x.py doc src/doc/nomicon
500 ./x.py doc src/doc/book library/std
501 ./x.py doc library/std --open
503 If no arguments are passed then everything is documented:
506 ./x.py doc --stage 1",
510 subcommand_help.push_str(
513 This subcommand accepts a number of paths to tools to build and run. For
516 ./x.py run src/tools/expand-yaml-anchors
518 At least a tool needs to be called.",
522 subcommand_help.push_str(&format!(
524 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
527 This subcommand accepts a 'profile' to use for builds. For example:
531 The profile is optional and you will be prompted interactively if it is not given.
532 The following profiles are available:
535 Profile::all_for_help(" ").trim_end()
540 // Get any optional paths which occur after the subcommand
541 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
543 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
544 let verbose = matches.opt_present("verbose");
546 // User passed in -h/--help?
547 if matches.opt_present("help") {
548 usage(0, &opts, verbose, &subcommand_help);
551 let cmd = match subcommand.as_str() {
552 "build" | "b" => Subcommand::Build { paths },
554 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
556 "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
557 "fix" => Subcommand::Fix { paths },
558 "test" | "t" => Subcommand::Test {
560 bless: matches.opt_present("bless"),
561 compare_mode: matches.opt_str("compare-mode"),
562 pass: matches.opt_str("pass"),
563 run: matches.opt_str("run"),
564 test_args: matches.opt_strs("test-args"),
565 rustc_args: matches.opt_strs("rustc-args"),
566 fail_fast: !matches.opt_present("no-fail-fast"),
567 rustfix_coverage: matches.opt_present("rustfix-coverage"),
568 doc_tests: if matches.opt_present("doc") {
570 } else if matches.opt_present("no-doc") {
576 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
577 "doc" | "d" => Subcommand::Doc { paths, open: matches.opt_present("open") },
579 if !paths.is_empty() {
580 println!("\nclean does not take a path argument\n");
581 usage(1, &opts, verbose, &subcommand_help);
584 Subcommand::Clean { all: matches.opt_present("all") }
586 "fmt" => Subcommand::Format { check: matches.opt_present("check"), paths },
587 "dist" => Subcommand::Dist { paths },
588 "install" => Subcommand::Install { paths },
590 if paths.is_empty() {
591 println!("\nrun requires at least a path!\n");
592 usage(1, &opts, verbose, &subcommand_help);
594 Subcommand::Run { paths }
597 let profile = if paths.len() > 1 {
598 println!("\nat most one profile can be passed to setup\n");
599 usage(1, &opts, verbose, &subcommand_help)
600 } else if let Some(path) = paths.pop() {
601 let profile_string = t!(path.into_os_string().into_string().map_err(
602 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
605 profile_string.parse().unwrap_or_else(|err| {
606 eprintln!("error: {}", err);
607 eprintln!("help: the available profiles are:");
608 eprint!("{}", Profile::all_for_help("- "));
609 std::process::exit(1);
612 t!(crate::setup::interactive_path())
614 Subcommand::Setup { profile }
617 usage(1, &opts, verbose, &subcommand_help);
621 if let Subcommand::Check { .. } = &cmd {
622 if matches.opt_str("keep-stage").is_some()
623 || matches.opt_str("keep-stage-std").is_some()
625 println!("--keep-stage not yet supported for x.py check");
631 verbose: matches.opt_count("verbose"),
632 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
633 dry_run: matches.opt_present("dry-run"),
634 on_fail: matches.opt_str("on-fail"),
635 rustc_error_format: matches.opt_str("error-format"),
636 json_output: matches.opt_present("json-output"),
638 .opt_strs("keep-stage")
640 .map(|j| j.parse().expect("`keep-stage` should be a number"))
642 keep_stage_std: matches
643 .opt_strs("keep-stage-std")
645 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
647 host: if matches.opt_present("host") {
649 split(&matches.opt_strs("host"))
651 .map(|x| TargetSelection::from_user(&x))
652 .collect::<Vec<_>>(),
657 target: if matches.opt_present("target") {
659 split(&matches.opt_strs("target"))
661 .map(|x| TargetSelection::from_user(&x))
662 .collect::<Vec<_>>(),
668 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
670 incremental: matches.opt_present("incremental"),
671 exclude: split(&matches.opt_strs("exclude"))
674 .collect::<Vec<_>>(),
675 include_default_paths: matches.opt_present("include-default-paths"),
676 deny_warnings: parse_deny_warnings(&matches),
677 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
678 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
681 .opt_get_default("color", Color::Auto)
682 .expect("`color` should be `always`, `never`, or `auto`"),
683 rust_profile_use: matches.opt_str("rust-profile-use"),
684 rust_profile_generate: matches.opt_str("rust-profile-generate"),
690 pub fn test_args(&self) -> Vec<&str> {
692 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
693 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
699 pub fn rustc_args(&self) -> Vec<&str> {
701 Subcommand::Test { ref rustc_args, .. } => {
702 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
708 pub fn fail_fast(&self) -> bool {
710 Subcommand::Test { fail_fast, .. } => fail_fast,
715 pub fn doc_tests(&self) -> DocTests {
717 Subcommand::Test { doc_tests, .. } => doc_tests,
722 pub fn bless(&self) -> bool {
724 Subcommand::Test { bless, .. } => bless,
729 pub fn rustfix_coverage(&self) -> bool {
731 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
736 pub fn compare_mode(&self) -> Option<&str> {
738 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
743 pub fn pass(&self) -> Option<&str> {
745 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
750 pub fn run(&self) -> Option<&str> {
752 Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
757 pub fn open(&self) -> bool {
759 Subcommand::Doc { open, .. } => open,
765 fn split(s: &[String]) -> Vec<String> {
766 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
769 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
770 match matches.opt_str("warnings").as_deref() {
771 Some("deny") => Some(true),
772 Some("warn") => Some(false),
774 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);