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
129 To learn more about a subcommand, run `./x.py <subcommand> -h`",
132 let mut opts = Options::new();
133 // Options common to all subcommands
134 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
135 opts.optflag("i", "incremental", "use incremental compilation");
136 opts.optopt("", "config", "TOML configuration file for build", "FILE");
137 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
138 opts.optmulti("", "host", "host targets to build", "HOST");
139 opts.optmulti("", "target", "target targets to build", "TARGET");
140 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
143 "include-default-paths",
144 "include default paths in addition to the provided ones",
146 opts.optopt("", "on-fail", "command to run on failure", "CMD");
147 opts.optflag("", "dry-run", "dry run; don't build anything");
151 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
152 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
158 "stage(s) to keep without recompiling \
159 (pass multiple times to keep e.g., both stages 0 and 1)",
165 "stage(s) of the standard library to keep without recompiling \
166 (pass multiple times to keep e.g., both stages 0 and 1)",
169 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
171 "number of jobs to run in parallel; \
172 defaults to {} (this host's logical CPU count)",
175 opts.optopt("j", "jobs", &j_msg, "JOBS");
176 opts.optflag("h", "help", "print this help message");
180 "if value is deny, will deny warnings, otherwise use default",
183 opts.optopt("", "error-format", "rustc error format", "FORMAT");
184 opts.optflag("", "json-output", "use message-format=json");
188 "whether rebuilding llvm should be skipped \
189 a VALUE of TRUE indicates that llvm will not be rebuilt \
190 VALUE overrides the skip-rebuild option in config.toml.",
194 // We can't use getopt to parse the options until we have completed specifying which
195 // options are valid, but under the current implementation, some options are conditional on
196 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
197 // complete the definition of the options. Then we can use the getopt::Matches object from
199 let subcommand = args.iter().find(|&s| {
218 let subcommand = match subcommand {
221 // No or an invalid subcommand -- show the general usage and subcommand help
222 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
224 println!("{}\n", subcommand_help);
225 let exit_code = if args.is_empty() { 0 } else { 1 };
226 process::exit(exit_code);
230 // Some subcommands get extra options
231 match subcommand.as_str() {
233 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
234 opts.optmulti("", "test-args", "extra arguments", "ARGS");
238 "extra options to pass the compiler when running tests",
241 opts.optflag("", "no-doc", "do not run doc tests");
242 opts.optflag("", "doc", "only run doc tests");
243 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
247 "mode describing what file the actual ui output will be compared to",
253 "force {check,build,run}-pass tests to this mode.",
254 "check | build | run",
259 "enable this to generate a Rustfix coverage file, which is saved in \
260 `/<build_base>/rustfix_missing_coverage.txt`",
264 opts.optflag("", "all-targets", "Check all targets");
267 opts.optmulti("", "test-args", "extra arguments", "ARGS");
270 opts.optflag("", "open", "open the docs in a browser");
273 opts.optflag("", "all", "clean all build artifacts");
276 opts.optflag("", "check", "check formatting instead of applying.");
282 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
283 let mut extra_help = String::new();
285 // All subcommands except `clean` can have an optional "Available paths" section
287 let config = Config::parse(&["build".to_string()]);
288 let build = Build::new(config);
290 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
291 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
292 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
294 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
299 println!("{}", opts.usage(subcommand_help));
300 if !extra_help.is_empty() {
301 println!("{}", extra_help);
303 process::exit(exit_code);
306 // Done specifying what options are possible, so do the getopts parsing
307 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
308 // Invalid argument/option format
309 println!("\n{}\n", e);
310 usage(1, &opts, false, &subcommand_help);
313 // Extra sanity check to make sure we didn't hit this crazy corner case:
315 // ./x.py --frobulate clean build
316 // ^-- option ^ ^- actual subcommand
317 // \_ arg to option could be mistaken as subcommand
318 let mut pass_sanity_check = true;
319 match matches.free.get(0) {
320 Some(check_subcommand) => {
321 if check_subcommand != subcommand {
322 pass_sanity_check = false;
326 pass_sanity_check = false;
329 if !pass_sanity_check {
330 println!("{}\n", subcommand_help);
332 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
333 You may need to move some options to after the subcommand.\n"
337 // Extra help text for some commands
338 match subcommand.as_str() {
340 subcommand_help.push_str(
343 This subcommand accepts a number of paths to directories to the crates
344 and/or artifacts to compile. For example:
346 ./x.py build library/core
347 ./x.py build library/core library/proc_macro
348 ./x.py build library/std --stage 1
350 If no arguments are passed then the complete artifacts for that stage are
354 ./x.py build --stage 1
356 For a quick build of a usable compiler, you can pass:
358 ./x.py build --stage 1 library/test
360 This will first build everything once (like `--stage 0` without further
361 arguments would), and then use the compiler built in stage 0 to build
362 library/test and its dependencies.
363 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
367 subcommand_help.push_str(
370 This subcommand accepts a number of paths to directories to the crates
371 and/or artifacts to compile. For example:
373 ./x.py check library/core
374 ./x.py check library/core library/proc_macro
376 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
377 also that since we use `cargo check`, by default this will automatically enable incremental
378 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
379 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
384 subcommand_help.push_str(
387 This subcommand accepts a number of paths to directories to the crates
388 and/or artifacts to run clippy against. For example:
390 ./x.py clippy library/core
391 ./x.py clippy library/core library/proc_macro",
395 subcommand_help.push_str(
398 This subcommand accepts a number of paths to directories to the crates
399 and/or artifacts to run `cargo fix` against. For example:
401 ./x.py fix library/core
402 ./x.py fix library/core library/proc_macro",
406 subcommand_help.push_str(
409 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
410 fails if it is not. For example:
417 subcommand_help.push_str(
420 This subcommand accepts a number of paths to test directories that
421 should be compiled and run. For example:
423 ./x.py test src/test/ui
424 ./x.py test library/std --test-args hash_map
425 ./x.py test library/std --stage 0 --no-doc
426 ./x.py test src/test/ui --bless
427 ./x.py test src/test/ui --compare-mode nll
429 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
430 just like `build library/std --stage N` it tests the compiler produced by the previous
433 Execute tool tests with a tool name argument:
437 If no arguments are passed then the complete artifacts for that stage are
441 ./x.py test --stage 1",
445 subcommand_help.push_str(
448 This subcommand accepts a number of paths to directories of documentation
449 to build. For example:
451 ./x.py doc src/doc/book
452 ./x.py doc src/doc/nomicon
453 ./x.py doc src/doc/book library/std
454 ./x.py doc library/std --open
456 If no arguments are passed then everything is documented:
459 ./x.py doc --stage 1",
463 subcommand_help.push_str(
466 This subcommand accepts a number of paths to tools to build and run. For
469 ./x.py run src/tools/expand-yaml-anchors
471 At least a tool needs to be called.",
475 subcommand_help.push_str(
478 This subcommand accepts a 'profile' to use for builds. For example:
482 The profile is optional and you will be prompted interactively if it is not given.",
487 // Get any optional paths which occur after the subcommand
488 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
490 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
491 let verbose = matches.opt_present("verbose");
493 // User passed in -h/--help?
494 if matches.opt_present("help") {
495 usage(0, &opts, verbose, &subcommand_help);
498 let cmd = match subcommand.as_str() {
499 "build" | "b" => Subcommand::Build { paths },
501 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
503 "clippy" => Subcommand::Clippy { paths },
504 "fix" => Subcommand::Fix { paths },
505 "test" | "t" => Subcommand::Test {
507 bless: matches.opt_present("bless"),
508 compare_mode: matches.opt_str("compare-mode"),
509 pass: matches.opt_str("pass"),
510 test_args: matches.opt_strs("test-args"),
511 rustc_args: matches.opt_strs("rustc-args"),
512 fail_fast: !matches.opt_present("no-fail-fast"),
513 rustfix_coverage: matches.opt_present("rustfix-coverage"),
514 doc_tests: if matches.opt_present("doc") {
516 } else if matches.opt_present("no-doc") {
522 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
523 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
525 if !paths.is_empty() {
526 println!("\nclean does not take a path argument\n");
527 usage(1, &opts, verbose, &subcommand_help);
530 Subcommand::Clean { all: matches.opt_present("all") }
532 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
533 "dist" => Subcommand::Dist { paths },
534 "install" => Subcommand::Install { paths },
536 if paths.is_empty() {
537 println!("\nrun requires at least a path!\n");
538 usage(1, &opts, verbose, &subcommand_help);
540 Subcommand::Run { paths }
543 let profile = if paths.len() > 1 {
544 println!("\nat most one profile can be passed to setup\n");
545 usage(1, &opts, verbose, &subcommand_help)
546 } else if let Some(path) = paths.pop() {
547 let profile_string = t!(path.into_os_string().into_string().map_err(
548 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
551 profile_string.parse().unwrap_or_else(|err| {
552 eprintln!("error: {}", err);
553 eprintln!("help: the available profiles are:");
554 eprint!("{}", Profile::all_for_help("- "));
555 std::process::exit(1);
558 t!(crate::setup::interactive_path())
560 Subcommand::Setup { profile }
563 usage(1, &opts, verbose, &subcommand_help);
567 if let Subcommand::Check { .. } = &cmd {
568 if matches.opt_str("stage").is_some() {
569 println!("--stage not supported for x.py check, always treated as stage 0");
572 if matches.opt_str("keep-stage").is_some()
573 || matches.opt_str("keep-stage-std").is_some()
575 println!("--keep-stage not supported for x.py check, only one stage available");
581 verbose: matches.opt_count("verbose"),
582 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
583 dry_run: matches.opt_present("dry-run"),
584 on_fail: matches.opt_str("on-fail"),
585 rustc_error_format: matches.opt_str("error-format"),
586 json_output: matches.opt_present("json-output"),
588 .opt_strs("keep-stage")
590 .map(|j| j.parse().expect("`keep-stage` should be a number"))
592 keep_stage_std: matches
593 .opt_strs("keep-stage-std")
595 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
597 host: if matches.opt_present("host") {
599 split(&matches.opt_strs("host"))
601 .map(|x| TargetSelection::from_user(&x))
602 .collect::<Vec<_>>(),
607 target: if matches.opt_present("target") {
609 split(&matches.opt_strs("target"))
611 .map(|x| TargetSelection::from_user(&x))
612 .collect::<Vec<_>>(),
618 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
620 incremental: matches.opt_present("incremental"),
621 exclude: split(&matches.opt_strs("exclude"))
624 .collect::<Vec<_>>(),
625 include_default_paths: matches.opt_present("include-default-paths"),
626 deny_warnings: parse_deny_warnings(&matches),
627 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
628 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
635 pub fn test_args(&self) -> Vec<&str> {
637 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
638 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
644 pub fn rustc_args(&self) -> Vec<&str> {
646 Subcommand::Test { ref rustc_args, .. } => {
647 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
653 pub fn fail_fast(&self) -> bool {
655 Subcommand::Test { fail_fast, .. } => fail_fast,
660 pub fn doc_tests(&self) -> DocTests {
662 Subcommand::Test { doc_tests, .. } => doc_tests,
667 pub fn bless(&self) -> bool {
669 Subcommand::Test { bless, .. } => bless,
674 pub fn rustfix_coverage(&self) -> bool {
676 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
681 pub fn compare_mode(&self) -> Option<&str> {
683 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
688 pub fn pass(&self) -> Option<&str> {
690 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
695 pub fn open(&self) -> bool {
697 Subcommand::Doc { open, .. } => open,
703 fn split(s: &[String]) -> Vec<String> {
704 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
707 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
708 match matches.opt_str("warnings").as_deref() {
709 Some("deny") => Some(true),
710 Some("warn") => Some(false),
712 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);