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;
12 use crate::builder::Builder;
13 use crate::config::{Config, TargetSelection};
14 use crate::{Build, DocTests};
16 /// Deserialized version of all flags for this compile.
18 pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
19 pub on_fail: Option<String>,
20 pub stage: Option<u32>,
21 pub keep_stage: Vec<u32>,
23 pub host: Option<Vec<TargetSelection>>,
24 pub target: Option<Vec<TargetSelection>>,
25 pub config: Option<PathBuf>,
26 pub jobs: Option<u32>,
28 pub incremental: bool,
29 pub exclude: Vec<PathBuf>,
30 pub rustc_error_format: Option<String>,
31 pub json_output: bool,
34 // This overrides the deny-warnings configuration option,
35 // which passes -Dwarnings to the compiler invocations.
37 // true => deny, false => warn
38 pub deny_warnings: Option<bool>,
40 pub llvm_skip_rebuild: Option<bool>,
65 /// Whether to automatically update stderr/stdout files
67 compare_mode: Option<String>,
69 test_args: Vec<String>,
70 rustc_args: Vec<String>,
73 rustfix_coverage: bool,
77 test_args: Vec<String>,
93 impl Default for Subcommand {
94 fn default() -> Subcommand {
95 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
100 pub fn parse(args: &[String]) -> Flags {
101 let mut subcommand_help = String::from(
103 Usage: x.py <subcommand> [options] [<paths>...]
106 build, b Compile either the compiler or libraries
107 check, c Compile either the compiler or libraries, using cargo check
108 clippy Run clippy (uses rustup/cargo-installed clippy binary)
111 test, t Build and run some test suites
112 bench Build and run some benchmarks
113 doc Build documentation
114 clean Clean out build directories
115 dist Build distribution artifacts
116 install Install distribution artifacts
117 run, r Run tools contained in this repository
119 To learn more about a subcommand, run `./x.py <subcommand> -h`",
122 let mut opts = Options::new();
123 // Options common to all subcommands
124 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
125 opts.optflag("i", "incremental", "use incremental compilation");
126 opts.optopt("", "config", "TOML configuration file for build", "FILE");
127 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
128 opts.optmulti("", "host", "host targets to build", "HOST");
129 opts.optmulti("", "target", "target targets to build", "TARGET");
130 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
131 opts.optopt("", "on-fail", "command to run on failure", "CMD");
132 opts.optflag("", "dry-run", "dry run; don't build anything");
136 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
137 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
143 "stage(s) to keep without recompiling \
144 (pass multiple times to keep e.g., both stages 0 and 1)",
147 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
149 "number of jobs to run in parallel; \
150 defaults to {} (this host's logical CPU count)",
153 opts.optopt("j", "jobs", &j_msg, "JOBS");
154 opts.optflag("h", "help", "print this help message");
158 "if value is deny, will deny warnings, otherwise use default",
161 opts.optopt("", "error-format", "rustc error format", "FORMAT");
162 opts.optflag("", "json-output", "use message-format=json");
166 "whether rebuilding llvm should be skipped \
167 a VALUE of TRUE indicates that llvm will not be rebuilt \
168 VALUE overrides the skip-rebuild option in config.toml.",
172 // We can't use getopt to parse the options until we have completed specifying which
173 // options are valid, but under the current implementation, some options are conditional on
174 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
175 // complete the definition of the options. Then we can use the getopt::Matches object from
177 let subcommand = args.iter().find(|&s| {
195 let subcommand = match subcommand {
198 // No or an invalid subcommand -- show the general usage and subcommand help
199 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
201 println!("{}\n", subcommand_help);
202 let exit_code = if args.is_empty() { 0 } else { 1 };
203 process::exit(exit_code);
207 // Some subcommands get extra options
208 match subcommand.as_str() {
210 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
211 opts.optmulti("", "test-args", "extra arguments", "ARGS");
215 "extra options to pass the compiler when running tests",
218 opts.optflag("", "no-doc", "do not run doc tests");
219 opts.optflag("", "doc", "only run doc tests");
220 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
224 "mode describing what file the actual ui output will be compared to",
230 "force {check,build,run}-pass tests to this mode.",
231 "check | build | run",
236 "enable this to generate a Rustfix coverage file, which is saved in \
237 `/<build_base>/rustfix_missing_coverage.txt`",
241 opts.optmulti("", "test-args", "extra arguments", "ARGS");
244 opts.optflag("", "open", "open the docs in a browser");
247 opts.optflag("", "all", "clean all build artifacts");
250 opts.optflag("", "check", "check formatting instead of applying.");
256 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
257 let mut extra_help = String::new();
259 // All subcommands except `clean` can have an optional "Available paths" section
261 let config = Config::parse(&["build".to_string()]);
262 let build = Build::new(config);
264 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
265 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
266 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
268 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
273 println!("{}", opts.usage(subcommand_help));
274 if !extra_help.is_empty() {
275 println!("{}", extra_help);
277 process::exit(exit_code);
280 // Done specifying what options are possible, so do the getopts parsing
281 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
282 // Invalid argument/option format
283 println!("\n{}\n", e);
284 usage(1, &opts, false, &subcommand_help);
287 // Extra sanity check to make sure we didn't hit this crazy corner case:
289 // ./x.py --frobulate clean build
290 // ^-- option ^ ^- actual subcommand
291 // \_ arg to option could be mistaken as subcommand
292 let mut pass_sanity_check = true;
293 match matches.free.get(0) {
294 Some(check_subcommand) => {
295 if check_subcommand != subcommand {
296 pass_sanity_check = false;
300 pass_sanity_check = false;
303 if !pass_sanity_check {
304 println!("{}\n", subcommand_help);
306 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
307 You may need to move some options to after the subcommand.\n"
311 // Extra help text for some commands
312 match subcommand.as_str() {
314 subcommand_help.push_str(
317 This subcommand accepts a number of paths to directories to the crates
318 and/or artifacts to compile. For example:
320 ./x.py build library/core
321 ./x.py build library/core library/proc_macro
322 ./x.py build library/std --stage 1
324 If no arguments are passed then the complete artifacts for that stage are
328 ./x.py build --stage 1
330 For a quick build of a usable compiler, you can pass:
332 ./x.py build --stage 1 library/test
334 This will first build everything once (like `--stage 0` without further
335 arguments would), and then use the compiler built in stage 0 to build
336 library/test and its dependencies.
337 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
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 check library/core
348 ./x.py check library/core library/proc_macro
350 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
351 also that since we use `cargo check`, by default this will automatically enable incremental
352 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
353 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
358 subcommand_help.push_str(
361 This subcommand accepts a number of paths to directories to the crates
362 and/or artifacts to run clippy against. For example:
364 ./x.py clippy library/core
365 ./x.py clippy library/core library/proc_macro",
369 subcommand_help.push_str(
372 This subcommand accepts a number of paths to directories to the crates
373 and/or artifacts to run `cargo fix` against. For example:
375 ./x.py fix library/core
376 ./x.py fix library/core library/proc_macro",
380 subcommand_help.push_str(
383 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
384 fails if it is not. For example:
391 subcommand_help.push_str(
394 This subcommand accepts a number of paths to test directories that
395 should be compiled and run. For example:
397 ./x.py test src/test/ui
398 ./x.py test library/std --test-args hash_map
399 ./x.py test library/std --stage 0 --no-doc
400 ./x.py test src/test/ui --bless
401 ./x.py test src/test/ui --compare-mode nll
403 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
404 just like `build library/std --stage N` it tests the compiler produced by the previous
407 Execute tool tests with a tool name argument:
411 If no arguments are passed then the complete artifacts for that stage are
415 ./x.py test --stage 1",
419 subcommand_help.push_str(
422 This subcommand accepts a number of paths to directories of documentation
423 to build. For example:
425 ./x.py doc src/doc/book
426 ./x.py doc src/doc/nomicon
427 ./x.py doc src/doc/book library/std
428 ./x.py doc library/std --open
430 If no arguments are passed then everything is documented:
433 ./x.py doc --stage 1",
437 subcommand_help.push_str(
440 This subcommand accepts a number of paths to tools to build and run. For
443 ./x.py run src/tools/expand-yaml-anchors
445 At least a tool needs to be called.",
450 // Get any optional paths which occur after the subcommand
451 let paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
453 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
454 let verbose = matches.opt_present("verbose");
456 // User passed in -h/--help?
457 if matches.opt_present("help") {
458 usage(0, &opts, verbose, &subcommand_help);
461 let cmd = match subcommand.as_str() {
462 "build" | "b" => Subcommand::Build { paths },
463 "check" | "c" => Subcommand::Check { paths },
464 "clippy" => Subcommand::Clippy { paths },
465 "fix" => Subcommand::Fix { paths },
466 "test" | "t" => Subcommand::Test {
468 bless: matches.opt_present("bless"),
469 compare_mode: matches.opt_str("compare-mode"),
470 pass: matches.opt_str("pass"),
471 test_args: matches.opt_strs("test-args"),
472 rustc_args: matches.opt_strs("rustc-args"),
473 fail_fast: !matches.opt_present("no-fail-fast"),
474 rustfix_coverage: matches.opt_present("rustfix-coverage"),
475 doc_tests: if matches.opt_present("doc") {
477 } else if matches.opt_present("no-doc") {
483 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
484 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
486 if !paths.is_empty() {
487 println!("\nclean does not take a path argument\n");
488 usage(1, &opts, verbose, &subcommand_help);
491 Subcommand::Clean { all: matches.opt_present("all") }
493 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
494 "dist" => Subcommand::Dist { paths },
495 "install" => Subcommand::Install { paths },
497 if paths.is_empty() {
498 println!("\nrun requires at least a path!\n");
499 usage(1, &opts, verbose, &subcommand_help);
501 Subcommand::Run { paths }
504 usage(1, &opts, verbose, &subcommand_help);
508 if let Subcommand::Check { .. } = &cmd {
509 if matches.opt_str("stage").is_some() {
510 println!("--stage not supported for x.py check, always treated as stage 0");
513 if matches.opt_str("keep-stage").is_some() {
514 println!("--keep-stage not supported for x.py check, only one stage available");
520 verbose: matches.opt_count("verbose"),
521 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
522 dry_run: matches.opt_present("dry-run"),
523 on_fail: matches.opt_str("on-fail"),
524 rustc_error_format: matches.opt_str("error-format"),
525 json_output: matches.opt_present("json-output"),
527 .opt_strs("keep-stage")
529 .map(|j| j.parse().expect("`keep-stage` should be a number"))
531 host: if matches.opt_present("host") {
533 split(&matches.opt_strs("host"))
535 .map(|x| TargetSelection::from_user(&x))
536 .collect::<Vec<_>>(),
541 target: if matches.opt_present("target") {
543 split(&matches.opt_strs("target"))
545 .map(|x| TargetSelection::from_user(&x))
546 .collect::<Vec<_>>(),
552 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
554 incremental: matches.opt_present("incremental"),
555 exclude: split(&matches.opt_strs("exclude"))
558 .collect::<Vec<_>>(),
559 deny_warnings: parse_deny_warnings(&matches),
560 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
561 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
568 pub fn test_args(&self) -> Vec<&str> {
570 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
571 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
577 pub fn rustc_args(&self) -> Vec<&str> {
579 Subcommand::Test { ref rustc_args, .. } => {
580 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
586 pub fn fail_fast(&self) -> bool {
588 Subcommand::Test { fail_fast, .. } => fail_fast,
593 pub fn doc_tests(&self) -> DocTests {
595 Subcommand::Test { doc_tests, .. } => doc_tests,
600 pub fn bless(&self) -> bool {
602 Subcommand::Test { bless, .. } => bless,
607 pub fn rustfix_coverage(&self) -> bool {
609 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
614 pub fn compare_mode(&self) -> Option<&str> {
616 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
621 pub fn pass(&self) -> Option<&str> {
623 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
628 pub fn open(&self) -> bool {
630 Subcommand::Doc { open, .. } => open,
636 fn split(s: &[String]) -> Vec<String> {
637 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
640 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
641 match matches.opt_str("warnings").as_deref() {
642 Some("deny") => Some(true),
643 Some("warn") => Some(false),
645 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);