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");
235 opts.optmulti("", "test-args", "extra arguments", "ARGS");
239 "extra options to pass the compiler when running tests",
242 opts.optflag("", "no-doc", "do not run doc tests");
243 opts.optflag("", "doc", "only run doc tests");
244 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
248 "mode describing what file the actual ui output will be compared to",
254 "force {check,build,run}-pass tests to this mode.",
255 "check | build | run",
260 "enable this to generate a Rustfix coverage file, which is saved in \
261 `/<build_base>/rustfix_missing_coverage.txt`",
265 opts.optflag("", "all-targets", "Check all targets");
268 opts.optmulti("", "test-args", "extra arguments", "ARGS");
271 opts.optflag("", "open", "open the docs in a browser");
274 opts.optflag("", "all", "clean all build artifacts");
277 opts.optflag("", "check", "check formatting instead of applying.");
283 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
284 let mut extra_help = String::new();
286 // All subcommands except `clean` can have an optional "Available paths" section
288 let config = Config::parse(&["build".to_string()]);
289 let build = Build::new(config);
291 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
292 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
293 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
295 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
300 println!("{}", opts.usage(subcommand_help));
301 if !extra_help.is_empty() {
302 println!("{}", extra_help);
304 process::exit(exit_code);
307 // Done specifying what options are possible, so do the getopts parsing
308 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
309 // Invalid argument/option format
310 println!("\n{}\n", e);
311 usage(1, &opts, false, &subcommand_help);
314 // Extra sanity check to make sure we didn't hit this crazy corner case:
316 // ./x.py --frobulate clean build
317 // ^-- option ^ ^- actual subcommand
318 // \_ arg to option could be mistaken as subcommand
319 let mut pass_sanity_check = true;
320 match matches.free.get(0) {
321 Some(check_subcommand) => {
322 if check_subcommand != subcommand {
323 pass_sanity_check = false;
327 pass_sanity_check = false;
330 if !pass_sanity_check {
331 println!("{}\n", subcommand_help);
333 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
334 You may need to move some options to after the subcommand.\n"
338 // Extra help text for some commands
339 match subcommand.as_str() {
341 subcommand_help.push_str(
344 This subcommand accepts a number of paths to directories to the crates
345 and/or artifacts to compile. For example:
347 ./x.py build library/core
348 ./x.py build library/core library/proc_macro
349 ./x.py build library/std --stage 1
351 If no arguments are passed then the complete artifacts for that stage are
355 ./x.py build --stage 1
357 For a quick build of a usable compiler, you can pass:
359 ./x.py build --stage 1 library/test
361 This will first build everything once (like `--stage 0` without further
362 arguments would), and then use the compiler built in stage 0 to build
363 library/test and its dependencies.
364 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
368 subcommand_help.push_str(
371 This subcommand accepts a number of paths to directories to the crates
372 and/or artifacts to compile. For example:
374 ./x.py check library/core
375 ./x.py check library/core library/proc_macro
377 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
378 also that since we use `cargo check`, by default this will automatically enable incremental
379 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
380 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
385 subcommand_help.push_str(
388 This subcommand accepts a number of paths to directories to the crates
389 and/or artifacts to run clippy against. For example:
391 ./x.py clippy library/core
392 ./x.py clippy library/core library/proc_macro",
396 subcommand_help.push_str(
399 This subcommand accepts a number of paths to directories to the crates
400 and/or artifacts to run `cargo fix` against. For example:
402 ./x.py fix library/core
403 ./x.py fix library/core library/proc_macro",
407 subcommand_help.push_str(
410 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
411 fails if it is not. For example:
418 subcommand_help.push_str(
421 This subcommand accepts a number of paths to test directories that
422 should be compiled and run. For example:
424 ./x.py test src/test/ui
425 ./x.py test library/std --test-args hash_map
426 ./x.py test library/std --stage 0 --no-doc
427 ./x.py test src/test/ui --bless
428 ./x.py test src/test/ui --compare-mode nll
430 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
431 just like `build library/std --stage N` it tests the compiler produced by the previous
434 Execute tool tests with a tool name argument:
438 If no arguments are passed then the complete artifacts for that stage are
442 ./x.py test --stage 1",
446 subcommand_help.push_str(
449 This subcommand accepts a number of paths to directories of documentation
450 to build. For example:
452 ./x.py doc src/doc/book
453 ./x.py doc src/doc/nomicon
454 ./x.py doc src/doc/book library/std
455 ./x.py doc library/std --open
457 If no arguments are passed then everything is documented:
460 ./x.py doc --stage 1",
464 subcommand_help.push_str(
467 This subcommand accepts a number of paths to tools to build and run. For
470 ./x.py run src/tools/expand-yaml-anchors
472 At least a tool needs to be called.",
476 subcommand_help.push_str(&format!(
478 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
481 This subcommand accepts a 'profile' to use for builds. For example:
485 The profile is optional and you will be prompted interactively if it is not given.
486 The following profiles are available:
489 Profile::all_for_help(" ").trim_end()
494 // Get any optional paths which occur after the subcommand
495 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
497 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
498 let verbose = matches.opt_present("verbose");
500 // User passed in -h/--help?
501 if matches.opt_present("help") {
502 usage(0, &opts, verbose, &subcommand_help);
505 let cmd = match subcommand.as_str() {
506 "build" | "b" => Subcommand::Build { paths },
508 Subcommand::Check { paths, all_targets: matches.opt_present("all-targets") }
510 "clippy" => Subcommand::Clippy { paths },
511 "fix" => Subcommand::Fix { paths },
512 "test" | "t" => Subcommand::Test {
514 bless: matches.opt_present("bless"),
515 compare_mode: matches.opt_str("compare-mode"),
516 pass: matches.opt_str("pass"),
517 test_args: matches.opt_strs("test-args"),
518 rustc_args: matches.opt_strs("rustc-args"),
519 fail_fast: !matches.opt_present("no-fail-fast"),
520 rustfix_coverage: matches.opt_present("rustfix-coverage"),
521 doc_tests: if matches.opt_present("doc") {
523 } else if matches.opt_present("no-doc") {
529 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
530 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
532 if !paths.is_empty() {
533 println!("\nclean does not take a path argument\n");
534 usage(1, &opts, verbose, &subcommand_help);
537 Subcommand::Clean { all: matches.opt_present("all") }
539 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
540 "dist" => Subcommand::Dist { paths },
541 "install" => Subcommand::Install { paths },
543 if paths.is_empty() {
544 println!("\nrun requires at least a path!\n");
545 usage(1, &opts, verbose, &subcommand_help);
547 Subcommand::Run { paths }
550 let profile = if paths.len() > 1 {
551 println!("\nat most one profile can be passed to setup\n");
552 usage(1, &opts, verbose, &subcommand_help)
553 } else if let Some(path) = paths.pop() {
554 let profile_string = t!(path.into_os_string().into_string().map_err(
555 |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
558 profile_string.parse().unwrap_or_else(|err| {
559 eprintln!("error: {}", err);
560 eprintln!("help: the available profiles are:");
561 eprint!("{}", Profile::all_for_help("- "));
562 std::process::exit(1);
565 t!(crate::setup::interactive_path())
567 Subcommand::Setup { profile }
570 usage(1, &opts, verbose, &subcommand_help);
574 if let Subcommand::Check { .. } = &cmd {
575 if matches.opt_str("stage").is_some() {
576 println!("--stage not supported for x.py check, always treated as stage 0");
579 if matches.opt_str("keep-stage").is_some()
580 || matches.opt_str("keep-stage-std").is_some()
582 println!("--keep-stage not supported for x.py check, only one stage available");
588 verbose: matches.opt_count("verbose"),
589 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
590 dry_run: matches.opt_present("dry-run"),
591 on_fail: matches.opt_str("on-fail"),
592 rustc_error_format: matches.opt_str("error-format"),
593 json_output: matches.opt_present("json-output"),
595 .opt_strs("keep-stage")
597 .map(|j| j.parse().expect("`keep-stage` should be a number"))
599 keep_stage_std: matches
600 .opt_strs("keep-stage-std")
602 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
604 host: if matches.opt_present("host") {
606 split(&matches.opt_strs("host"))
608 .map(|x| TargetSelection::from_user(&x))
609 .collect::<Vec<_>>(),
614 target: if matches.opt_present("target") {
616 split(&matches.opt_strs("target"))
618 .map(|x| TargetSelection::from_user(&x))
619 .collect::<Vec<_>>(),
625 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
627 incremental: matches.opt_present("incremental"),
628 exclude: split(&matches.opt_strs("exclude"))
631 .collect::<Vec<_>>(),
632 include_default_paths: matches.opt_present("include-default-paths"),
633 deny_warnings: parse_deny_warnings(&matches),
634 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
635 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
642 pub fn test_args(&self) -> Vec<&str> {
644 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
645 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
651 pub fn rustc_args(&self) -> Vec<&str> {
653 Subcommand::Test { ref rustc_args, .. } => {
654 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
660 pub fn fail_fast(&self) -> bool {
662 Subcommand::Test { fail_fast, .. } => fail_fast,
667 pub fn doc_tests(&self) -> DocTests {
669 Subcommand::Test { doc_tests, .. } => doc_tests,
674 pub fn bless(&self) -> bool {
676 Subcommand::Test { bless, .. } => bless,
681 pub fn rustfix_coverage(&self) -> bool {
683 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
688 pub fn compare_mode(&self) -> Option<&str> {
690 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
695 pub fn pass(&self) -> Option<&str> {
697 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
702 pub fn open(&self) -> bool {
704 Subcommand::Doc { open, .. } => open,
710 fn split(s: &[String]) -> Vec<String> {
711 s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
714 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
715 match matches.opt_str("warnings").as_deref() {
716 Some("deny") => Some(true),
717 Some("warn") => Some(false),
719 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);