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::{Build, DocTests};
17 /// Deserialized version of all flags for this compile.
19 pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
20 pub on_fail: Option<String>,
21 pub stage: Option<u32>,
22 pub keep_stage: Vec<u32>,
23 pub keep_stage_std: Vec<u32>,
25 pub host: Option<Vec<TargetSelection>>,
26 pub target: Option<Vec<TargetSelection>>,
27 pub config: Option<PathBuf>,
28 pub jobs: Option<u32>,
30 pub incremental: bool,
31 pub exclude: Vec<PathBuf>,
32 pub rustc_error_format: Option<String>,
33 pub json_output: bool,
36 // This overrides the deny-warnings configuration option,
37 // which passes -Dwarnings to the compiler invocations.
39 // true => deny, false => warn
40 pub deny_warnings: Option<bool>,
42 pub llvm_skip_rebuild: Option<bool>,
67 /// Whether to automatically update stderr/stdout files
69 compare_mode: Option<String>,
71 test_args: Vec<String>,
72 rustc_args: Vec<String>,
75 rustfix_coverage: bool,
79 test_args: Vec<String>,
98 impl Default for Subcommand {
99 fn default() -> Subcommand {
100 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
105 pub fn parse(args: &[String]) -> Flags {
106 let mut subcommand_help = String::from(
108 Usage: x.py <subcommand> [options] [<paths>...]
111 build, b Compile either the compiler or libraries
112 check, c Compile either the compiler or libraries, using cargo check
113 clippy Run clippy (uses rustup/cargo-installed clippy binary)
116 test, t Build and run some test suites
117 bench Build and run some benchmarks
118 doc Build documentation
119 clean Clean out build directories
120 dist Build distribution artifacts
121 install Install distribution artifacts
122 run, r Run tools contained in this repository
124 To learn more about a subcommand, run `./x.py <subcommand> -h`",
127 let mut opts = Options::new();
128 // Options common to all subcommands
129 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
130 opts.optflag("i", "incremental", "use incremental compilation");
131 opts.optopt("", "config", "TOML configuration file for build", "FILE");
132 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
133 opts.optmulti("", "host", "host targets to build", "HOST");
134 opts.optmulti("", "target", "target targets to build", "TARGET");
135 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
136 opts.optopt("", "on-fail", "command to run on failure", "CMD");
137 opts.optflag("", "dry-run", "dry run; don't build anything");
141 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
142 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
148 "stage(s) to keep without recompiling \
149 (pass multiple times to keep e.g., both stages 0 and 1)",
155 "stage(s) of the standard library to keep without recompiling \
156 (pass multiple times to keep e.g., both stages 0 and 1)",
159 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
161 "number of jobs to run in parallel; \
162 defaults to {} (this host's logical CPU count)",
165 opts.optopt("j", "jobs", &j_msg, "JOBS");
166 opts.optflag("h", "help", "print this help message");
170 "if value is deny, will deny warnings, otherwise use default",
173 opts.optopt("", "error-format", "rustc error format", "FORMAT");
174 opts.optflag("", "json-output", "use message-format=json");
178 "whether rebuilding llvm should be skipped \
179 a VALUE of TRUE indicates that llvm will not be rebuilt \
180 VALUE overrides the skip-rebuild option in config.toml.",
184 // We can't use getopt to parse the options until we have completed specifying which
185 // options are valid, but under the current implementation, some options are conditional on
186 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
187 // complete the definition of the options. Then we can use the getopt::Matches object from
189 let subcommand = args.iter().find(|&s| {
208 let subcommand = match subcommand {
211 // No or an invalid subcommand -- show the general usage and subcommand help
212 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
214 println!("{}\n", subcommand_help);
215 let exit_code = if args.is_empty() { 0 } else { 1 };
216 process::exit(exit_code);
220 // Some subcommands get extra options
221 match subcommand.as_str() {
223 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
224 opts.optmulti("", "test-args", "extra arguments", "ARGS");
228 "extra options to pass the compiler when running tests",
231 opts.optflag("", "no-doc", "do not run doc tests");
232 opts.optflag("", "doc", "only run doc tests");
233 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
237 "mode describing what file the actual ui output will be compared to",
243 "force {check,build,run}-pass tests to this mode.",
244 "check | build | run",
249 "enable this to generate a Rustfix coverage file, which is saved in \
250 `/<build_base>/rustfix_missing_coverage.txt`",
254 opts.optmulti("", "test-args", "extra arguments", "ARGS");
257 opts.optflag("", "open", "open the docs in a browser");
260 opts.optflag("", "all", "clean all build artifacts");
263 opts.optflag("", "check", "check formatting instead of applying.");
269 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
270 let mut extra_help = String::new();
272 // All subcommands except `clean` can have an optional "Available paths" section
274 let config = Config::parse(&["build".to_string()]);
275 let build = Build::new(config);
277 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
278 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
279 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
281 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
286 println!("{}", opts.usage(subcommand_help));
287 if !extra_help.is_empty() {
288 println!("{}", extra_help);
290 process::exit(exit_code);
293 // Done specifying what options are possible, so do the getopts parsing
294 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
295 // Invalid argument/option format
296 println!("\n{}\n", e);
297 usage(1, &opts, false, &subcommand_help);
300 // Extra sanity check to make sure we didn't hit this crazy corner case:
302 // ./x.py --frobulate clean build
303 // ^-- option ^ ^- actual subcommand
304 // \_ arg to option could be mistaken as subcommand
305 let mut pass_sanity_check = true;
306 match matches.free.get(0) {
307 Some(check_subcommand) => {
308 if check_subcommand != subcommand {
309 pass_sanity_check = false;
313 pass_sanity_check = false;
316 if !pass_sanity_check {
317 println!("{}\n", subcommand_help);
319 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
320 You may need to move some options to after the subcommand.\n"
324 // Extra help text for some commands
325 match subcommand.as_str() {
327 subcommand_help.push_str(
330 This subcommand accepts a number of paths to directories to the crates
331 and/or artifacts to compile. For example:
333 ./x.py build library/core
334 ./x.py build library/core library/proc_macro
335 ./x.py build library/std --stage 1
337 If no arguments are passed then the complete artifacts for that stage are
341 ./x.py build --stage 1
343 For a quick build of a usable compiler, you can pass:
345 ./x.py build --stage 1 library/test
347 This will first build everything once (like `--stage 0` without further
348 arguments would), and then use the compiler built in stage 0 to build
349 library/test and its dependencies.
350 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
354 subcommand_help.push_str(
357 This subcommand accepts a number of paths to directories to the crates
358 and/or artifacts to compile. For example:
360 ./x.py check library/core
361 ./x.py check library/core library/proc_macro
363 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
364 also that since we use `cargo check`, by default this will automatically enable incremental
365 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
366 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
371 subcommand_help.push_str(
374 This subcommand accepts a number of paths to directories to the crates
375 and/or artifacts to run clippy against. For example:
377 ./x.py clippy library/core
378 ./x.py clippy library/core library/proc_macro",
382 subcommand_help.push_str(
385 This subcommand accepts a number of paths to directories to the crates
386 and/or artifacts to run `cargo fix` against. For example:
388 ./x.py fix library/core
389 ./x.py fix library/core library/proc_macro",
393 subcommand_help.push_str(
396 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
397 fails if it is not. For example:
404 subcommand_help.push_str(
407 This subcommand accepts a number of paths to test directories that
408 should be compiled and run. For example:
410 ./x.py test src/test/ui
411 ./x.py test library/std --test-args hash_map
412 ./x.py test library/std --stage 0 --no-doc
413 ./x.py test src/test/ui --bless
414 ./x.py test src/test/ui --compare-mode nll
416 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
417 just like `build library/std --stage N` it tests the compiler produced by the previous
420 Execute tool tests with a tool name argument:
424 If no arguments are passed then the complete artifacts for that stage are
428 ./x.py test --stage 1",
432 subcommand_help.push_str(
435 This subcommand accepts a number of paths to directories of documentation
436 to build. For example:
438 ./x.py doc src/doc/book
439 ./x.py doc src/doc/nomicon
440 ./x.py doc src/doc/book library/std
441 ./x.py doc library/std --open
443 If no arguments are passed then everything is documented:
446 ./x.py doc --stage 1",
450 subcommand_help.push_str(
453 This subcommand accepts a number of paths to tools to build and run. For
456 ./x.py run src/tools/expand-yaml-anchors
458 At least a tool needs to be called.",
462 subcommand_help.push_str(
465 This subcommand accepts a 'profile' to use for builds. For example:
469 The profile is optional and you will be prompted interactively if it is not given.",
474 // Get any optional paths which occur after the subcommand
475 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
477 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
478 let verbose = matches.opt_present("verbose");
480 // User passed in -h/--help?
481 if matches.opt_present("help") {
482 usage(0, &opts, verbose, &subcommand_help);
485 let cmd = match subcommand.as_str() {
486 "build" | "b" => Subcommand::Build { paths },
487 "check" | "c" => Subcommand::Check { paths },
488 "clippy" => Subcommand::Clippy { paths },
489 "fix" => Subcommand::Fix { paths },
490 "test" | "t" => Subcommand::Test {
492 bless: matches.opt_present("bless"),
493 compare_mode: matches.opt_str("compare-mode"),
494 pass: matches.opt_str("pass"),
495 test_args: matches.opt_strs("test-args"),
496 rustc_args: matches.opt_strs("rustc-args"),
497 fail_fast: !matches.opt_present("no-fail-fast"),
498 rustfix_coverage: matches.opt_present("rustfix-coverage"),
499 doc_tests: if matches.opt_present("doc") {
501 } else if matches.opt_present("no-doc") {
507 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
508 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
510 if !paths.is_empty() {
511 println!("\nclean does not take a path argument\n");
512 usage(1, &opts, verbose, &subcommand_help);
515 Subcommand::Clean { all: matches.opt_present("all") }
517 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
518 "dist" => Subcommand::Dist { paths },
519 "install" => Subcommand::Install { paths },
521 if paths.is_empty() {
522 println!("\nrun requires at least a path!\n");
523 usage(1, &opts, verbose, &subcommand_help);
525 Subcommand::Run { paths }
528 let path = if paths.len() > 1 {
529 println!("\nat most one profile can be passed to setup\n");
530 usage(1, &opts, verbose, &subcommand_help)
531 } else if let Some(path) = paths.pop() {
532 t!(path.into_os_string().into_string().map_err(|path| format!(
533 "{} is not a valid UTF8 string",
534 path.to_string_lossy()
537 t!(crate::setup::interactive_path())
539 Subcommand::Setup { path }
542 usage(1, &opts, verbose, &subcommand_help);
546 if let Subcommand::Check { .. } = &cmd {
547 if matches.opt_str("stage").is_some() {
548 println!("--stage not supported for x.py check, always treated as stage 0");
551 if matches.opt_str("keep-stage").is_some()
552 || matches.opt_str("keep-stage-std").is_some()
554 println!("--keep-stage not supported for x.py check, only one stage available");
560 verbose: matches.opt_count("verbose"),
561 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
562 dry_run: matches.opt_present("dry-run"),
563 on_fail: matches.opt_str("on-fail"),
564 rustc_error_format: matches.opt_str("error-format"),
565 json_output: matches.opt_present("json-output"),
567 .opt_strs("keep-stage")
569 .map(|j| j.parse().expect("`keep-stage` should be a number"))
571 keep_stage_std: matches
572 .opt_strs("keep-stage-std")
574 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
576 host: if matches.opt_present("host") {
578 split(&matches.opt_strs("host"))
580 .map(|x| TargetSelection::from_user(&x))
581 .collect::<Vec<_>>(),
586 target: if matches.opt_present("target") {
588 split(&matches.opt_strs("target"))
590 .map(|x| TargetSelection::from_user(&x))
591 .collect::<Vec<_>>(),
597 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
599 incremental: matches.opt_present("incremental"),
600 exclude: split(&matches.opt_strs("exclude"))
603 .collect::<Vec<_>>(),
604 deny_warnings: parse_deny_warnings(&matches),
605 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
606 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
613 pub fn test_args(&self) -> Vec<&str> {
615 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
616 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
622 pub fn rustc_args(&self) -> Vec<&str> {
624 Subcommand::Test { ref rustc_args, .. } => {
625 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
631 pub fn fail_fast(&self) -> bool {
633 Subcommand::Test { fail_fast, .. } => fail_fast,
638 pub fn doc_tests(&self) -> DocTests {
640 Subcommand::Test { doc_tests, .. } => doc_tests,
645 pub fn bless(&self) -> bool {
647 Subcommand::Test { bless, .. } => bless,
652 pub fn rustfix_coverage(&self) -> bool {
654 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
659 pub fn compare_mode(&self) -> Option<&str> {
661 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
666 pub fn pass(&self) -> Option<&str> {
668 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
673 pub fn open(&self) -> bool {
675 Subcommand::Doc { open, .. } => open,
681 fn split(s: &[String]) -> Vec<String> {
682 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
685 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
686 match matches.opt_str("warnings").as_deref() {
687 Some("deny") => Some(true),
688 Some("warn") => Some(false),
690 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);