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};
18 /// Deserialized version of all flags for this compile.
20 pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
21 pub on_fail: Option<String>,
22 pub stage: Option<u32>,
23 pub keep_stage: Vec<u32>,
24 pub keep_stage_std: Vec<u32>,
26 pub host: Option<Vec<TargetSelection>>,
27 pub target: Option<Vec<TargetSelection>>,
28 pub config: Option<PathBuf>,
29 pub jobs: Option<u32>,
31 pub incremental: bool,
32 pub exclude: Vec<PathBuf>,
33 pub include_default_paths: bool,
34 pub rustc_error_format: Option<String>,
35 pub json_output: bool,
38 // This overrides the deny-warnings configuration option,
39 // which passes -Dwarnings to the compiler invocations.
41 // true => deny, false => warn
42 pub deny_warnings: Option<bool>,
44 pub llvm_skip_rebuild: Option<bool>,
52 // Whether to run checking over all targets (e.g., unit / integration
72 /// Whether to automatically update stderr/stdout files
74 compare_mode: Option<String>,
76 test_args: Vec<String>,
77 rustc_args: Vec<String>,
80 rustfix_coverage: bool,
84 test_args: Vec<String>,
103 impl Default for Subcommand {
104 fn default() -> Subcommand {
105 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
110 pub fn parse(args: &[String]) -> Flags {
111 let mut subcommand_help = String::from(
113 Usage: x.py <subcommand> [options] [<paths>...]
116 build, b Compile either the compiler or libraries
117 check, c Compile either the compiler or libraries, using cargo check
118 clippy Run clippy (uses rustup/cargo-installed clippy binary)
121 test, t Build and run some test suites
122 bench Build and run some benchmarks
123 doc Build documentation
124 clean Clean out build directories
125 dist Build distribution artifacts
126 install Install distribution artifacts
127 run, r Run tools contained in this repository
128 setup Create a config.toml (making it easier to use `x.py` itself)
130 To learn more about a subcommand, run `./x.py <subcommand> -h`",
133 let mut opts = Options::new();
134 // Options common to all subcommands
135 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
136 opts.optflag("i", "incremental", "use incremental compilation");
137 opts.optopt("", "config", "TOML configuration file for build", "FILE");
138 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
139 opts.optmulti("", "host", "host targets to build", "HOST");
140 opts.optmulti("", "target", "target targets to build", "TARGET");
141 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
144 "include-default-paths",
145 "include default paths in addition to the provided ones",
147 opts.optopt("", "on-fail", "command to run on failure", "CMD");
148 opts.optflag("", "dry-run", "dry run; don't build anything");
152 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
153 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
159 "stage(s) to keep without recompiling \
160 (pass multiple times to keep e.g., both stages 0 and 1)",
166 "stage(s) of the standard library to keep without recompiling \
167 (pass multiple times to keep e.g., both stages 0 and 1)",
170 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
172 "number of jobs to run in parallel; \
173 defaults to {} (this host's logical CPU count)",
176 opts.optopt("j", "jobs", &j_msg, "JOBS");
177 opts.optflag("h", "help", "print this help message");
181 "if value is deny, will deny warnings, otherwise use default",
184 opts.optopt("", "error-format", "rustc error format", "FORMAT");
185 opts.optflag("", "json-output", "use message-format=json");
189 "whether rebuilding llvm should be skipped \
190 a VALUE of TRUE indicates that llvm will not be rebuilt \
191 VALUE overrides the skip-rebuild option in config.toml.",
195 // We can't use getopt to parse the options until we have completed specifying which
196 // options are valid, but under the current implementation, some options are conditional on
197 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
198 // complete the definition of the options. Then we can use the getopt::Matches object from
200 let subcommand = args.iter().find(|&s| {
219 let subcommand = match subcommand {
222 // No or an invalid subcommand -- show the general usage and subcommand help
223 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
225 println!("{}\n", subcommand_help);
226 let exit_code = if args.is_empty() { 0 } else { 1 };
227 process::exit(exit_code);
231 // Some subcommands get extra options
232 match subcommand.as_str() {
234 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
238 "extra arguments to be passed for the test tool being used \
239 (e.g. libtest, compiletest or rustdoc)",
245 "extra options to pass the compiler when running tests",
248 opts.optflag("", "no-doc", "do not run doc tests");
249 opts.optflag("", "doc", "only run doc tests");
250 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
254 "mode describing what file the actual ui output will be compared to",
260 "force {check,build,run}-pass tests to this mode.",
261 "check | build | run",
266 "enable this to generate a Rustfix coverage file, which is saved in \
267 `/<build_base>/rustfix_missing_coverage.txt`",
271 opts.optflag("", "all-targets", "Check all targets");
274 opts.optmulti("", "test-args", "extra arguments", "ARGS");
277 opts.optflag("", "open", "open the docs in a browser");
280 opts.optflag("", "all", "clean all build artifacts");
283 opts.optflag("", "check", "check formatting instead of applying.");
289 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
290 let mut extra_help = String::new();
292 // All subcommands except `clean` can have an optional "Available paths" section
294 let config = Config::parse(&["build".to_string()]);
295 let build = Build::new(config);
297 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
298 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
299 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
301 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
306 println!("{}", opts.usage(subcommand_help));
307 if !extra_help.is_empty() {
308 println!("{}", extra_help);
310 process::exit(exit_code);
313 // Done specifying what options are possible, so do the getopts parsing
314 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
315 // Invalid argument/option format
316 println!("\n{}\n", e);
317 usage(1, &opts, false, &subcommand_help);
320 // Extra sanity check to make sure we didn't hit this crazy corner case:
322 // ./x.py --frobulate clean build
323 // ^-- option ^ ^- actual subcommand
324 // \_ arg to option could be mistaken as subcommand
325 let mut pass_sanity_check = true;
326 match matches.free.get(0) {
327 Some(check_subcommand) => {
328 if check_subcommand != subcommand {
329 pass_sanity_check = false;
333 pass_sanity_check = false;
336 if !pass_sanity_check {
337 println!("{}\n", subcommand_help);
339 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
340 You may need to move some options to after the subcommand.\n"
344 // Extra help text for some commands
345 match subcommand.as_str() {
347 subcommand_help.push_str(
350 This subcommand accepts a number of paths to directories to the crates
351 and/or artifacts to compile. For example:
353 ./x.py build library/core
354 ./x.py build library/core library/proc_macro
355 ./x.py build library/std --stage 1
357 If no arguments are passed then the complete artifacts for that stage are
361 ./x.py build --stage 1
363 For a quick build of a usable compiler, you can pass:
365 ./x.py build --stage 1 library/test
367 This will first build everything once (like `--stage 0` without further
368 arguments would), and then use the compiler built in stage 0 to build
369 library/test and its dependencies.
370 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
374 subcommand_help.push_str(
377 This subcommand accepts a number of paths to directories to the crates
378 and/or artifacts to compile. For example:
380 ./x.py check library/core
381 ./x.py check library/core library/proc_macro
383 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
384 also that since we use `cargo check`, by default this will automatically enable incremental
385 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
386 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
391 subcommand_help.push_str(
394 This subcommand accepts a number of paths to directories to the crates
395 and/or artifacts to run clippy against. For example:
397 ./x.py clippy library/core
398 ./x.py clippy library/core library/proc_macro",
402 subcommand_help.push_str(
405 This subcommand accepts a number of paths to directories to the crates
406 and/or artifacts to run `cargo fix` against. For example:
408 ./x.py fix library/core
409 ./x.py fix library/core library/proc_macro",
413 subcommand_help.push_str(
416 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
417 fails if it is not. For example:
424 subcommand_help.push_str(
427 This subcommand accepts a number of paths to test directories that
428 should be compiled and run. For example:
430 ./x.py test src/test/ui
431 ./x.py test library/std --test-args hash_map
432 ./x.py test library/std --stage 0 --no-doc
433 ./x.py test src/test/ui --bless
434 ./x.py test src/test/ui --compare-mode nll
436 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
437 just like `build library/std --stage N` it tests the compiler produced by the previous
440 Execute tool tests with a tool name argument:
444 If no arguments are passed then the complete artifacts for that stage are
448 ./x.py test --stage 1",
452 subcommand_help.push_str(
455 This subcommand accepts a number of paths to directories of documentation
456 to build. For example:
458 ./x.py doc src/doc/book
459 ./x.py doc src/doc/nomicon
460 ./x.py doc src/doc/book library/std
461 ./x.py doc library/std --open
463 If no arguments are passed then everything is documented:
466 ./x.py doc --stage 1",
470 subcommand_help.push_str(
473 This subcommand accepts a number of paths to tools to build and run. For
476 ./x.py run src/tools/expand-yaml-anchors
478 At least a tool needs to be called.",
482 subcommand_help.push_str(&format!(
484 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
487 This subcommand accepts a 'profile' to use for builds. For example:
491 The profile is optional and you will be prompted interactively if it is not given.
492 The following profiles are available:
495 Profile::all_for_help(" ").trim_end()
500 // Get any optional paths which occur after the subcommand
501 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
503 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
504 let verbose = matches.opt_present("verbose");
506 // User passed in -h/--help?
507 if matches.opt_present("help") {
508 usage(0, &opts, verbose, &subcommand_help);
511 let cmd = match subcommand.as_str() {
512 "build" | "b" => Subcommand::Build { paths },
514 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
516 "clippy" => Subcommand::Clippy { paths },
517 "fix" => Subcommand::Fix { paths },
518 "test" | "t" => Subcommand::Test {
520 bless: matches.opt_present("bless"),
521 compare_mode: matches.opt_str("compare-mode"),
522 pass: matches.opt_str("pass"),
523 test_args: matches.opt_strs("test-args"),
524 rustc_args: matches.opt_strs("rustc-args"),
525 fail_fast: !matches.opt_present("no-fail-fast"),
526 rustfix_coverage: matches.opt_present("rustfix-coverage"),
527 doc_tests: if matches.opt_present("doc") {
529 } else if matches.opt_present("no-doc") {
535 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
536 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
538 if !paths.is_empty() {
539 println!("\nclean does not take a path argument\n");
540 usage(1, &opts, verbose, &subcommand_help);
543 Subcommand::Clean { all: matches.opt_present("all") }
545 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
546 "dist" => Subcommand::Dist { paths },
547 "install" => Subcommand::Install { paths },
549 if paths.is_empty() {
550 println!("\nrun requires at least a path!\n");
551 usage(1, &opts, verbose, &subcommand_help);
553 Subcommand::Run { paths }
556 let profile = if paths.len() > 1 {
557 println!("\nat most one profile can be passed to setup\n");
558 usage(1, &opts, verbose, &subcommand_help)
559 } else if let Some(path) = paths.pop() {
560 let profile_string = t!(path.into_os_string().into_string().map_err(
561 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
564 profile_string.parse().unwrap_or_else(|err| {
565 eprintln!("error: {}", err);
566 eprintln!("help: the available profiles are:");
567 eprint!("{}", Profile::all_for_help("- "));
568 std::process::exit(1);
571 t!(crate::setup::interactive_path())
573 Subcommand::Setup { profile }
576 usage(1, &opts, verbose, &subcommand_help);
580 if let Subcommand::Check { .. } = &cmd {
581 if matches.opt_str("stage").is_some() {
582 println!("--stage not supported for x.py check, always treated as stage 0");
585 if matches.opt_str("keep-stage").is_some()
586 || matches.opt_str("keep-stage-std").is_some()
588 println!("--keep-stage not supported for x.py check, only one stage available");
594 verbose: matches.opt_count("verbose"),
595 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
596 dry_run: matches.opt_present("dry-run"),
597 on_fail: matches.opt_str("on-fail"),
598 rustc_error_format: matches.opt_str("error-format"),
599 json_output: matches.opt_present("json-output"),
601 .opt_strs("keep-stage")
603 .map(|j| j.parse().expect("`keep-stage` should be a number"))
605 keep_stage_std: matches
606 .opt_strs("keep-stage-std")
608 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
610 host: if matches.opt_present("host") {
612 split(&matches.opt_strs("host"))
614 .map(|x| TargetSelection::from_user(&x))
615 .collect::<Vec<_>>(),
620 target: if matches.opt_present("target") {
622 split(&matches.opt_strs("target"))
624 .map(|x| TargetSelection::from_user(&x))
625 .collect::<Vec<_>>(),
631 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
633 incremental: matches.opt_present("incremental"),
634 exclude: split(&matches.opt_strs("exclude"))
637 .collect::<Vec<_>>(),
638 include_default_paths: matches.opt_present("include-default-paths"),
639 deny_warnings: parse_deny_warnings(&matches),
640 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
641 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
648 pub fn test_args(&self) -> Vec<&str> {
650 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
651 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
657 pub fn rustc_args(&self) -> Vec<&str> {
659 Subcommand::Test { ref rustc_args, .. } => {
660 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
666 pub fn fail_fast(&self) -> bool {
668 Subcommand::Test { fail_fast, .. } => fail_fast,
673 pub fn doc_tests(&self) -> DocTests {
675 Subcommand::Test { doc_tests, .. } => doc_tests,
680 pub fn bless(&self) -> bool {
682 Subcommand::Test { bless, .. } => bless,
687 pub fn rustfix_coverage(&self) -> bool {
689 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
694 pub fn compare_mode(&self) -> Option<&str> {
696 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
701 pub fn pass(&self) -> Option<&str> {
703 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
708 pub fn open(&self) -> bool {
710 Subcommand::Doc { open, .. } => open,
716 fn split(s: &[String]) -> Vec<String> {
717 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
720 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
721 match matches.opt_str("warnings").as_deref() {
722 Some("deny") => Some(true),
723 Some("warn") => Some(false),
725 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);