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: Vec<TargetSelection>,
24 pub target: 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 extra_help = String::new();
102 let mut subcommand_help = String::from(
104 Usage: x.py <subcommand> [options] [<paths>...]
107 build, b Compile either the compiler or libraries
108 check, c Compile either the compiler or libraries, using cargo check
109 clippy Run clippy (uses rustup/cargo-installed clippy binary)
112 test, t Build and run some test suites
113 bench Build and run some benchmarks
114 doc Build documentation
115 clean Clean out build directories
116 dist Build distribution artifacts
117 install Install distribution artifacts
118 run, r Run tools contained in this repository
120 To learn more about a subcommand, run `./x.py <subcommand> -h`",
123 let mut opts = Options::new();
124 // Options common to all subcommands
125 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
126 opts.optflag("i", "incremental", "use incremental compilation");
127 opts.optopt("", "config", "TOML configuration file for build", "FILE");
128 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
129 opts.optmulti("", "host", "host targets to build", "HOST");
130 opts.optmulti("", "target", "target targets to build", "TARGET");
131 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
132 opts.optopt("", "on-fail", "command to run on failure", "CMD");
133 opts.optflag("", "dry-run", "dry run; don't build anything");
137 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
138 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
144 "stage(s) to keep without recompiling \
145 (pass multiple times to keep e.g., both stages 0 and 1)",
148 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
150 "number of jobs to run in parallel; \
151 defaults to {} (this host's logical CPU count)",
154 opts.optopt("j", "jobs", &j_msg, "JOBS");
155 opts.optflag("h", "help", "print this help message");
159 "if value is deny, will deny warnings, otherwise use default",
162 opts.optopt("", "error-format", "rustc error format", "FORMAT");
163 opts.optflag("", "json-output", "use message-format=json");
167 "whether rebuilding llvm should be skipped \
168 a VALUE of TRUE indicates that llvm will not be rebuilt \
169 VALUE overrides the skip-rebuild option in config.toml.",
175 |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
176 println!("{}", opts.usage(subcommand_help));
177 if !extra_help.is_empty() {
178 println!("{}", extra_help);
180 process::exit(exit_code);
183 // We can't use getopt to parse the options until we have completed specifying which
184 // options are valid, but under the current implementation, some options are conditional on
185 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
186 // complete the definition of the options. Then we can use the getopt::Matches object from
188 let subcommand = args.iter().find(|&s| {
206 let subcommand = match subcommand {
209 // No or an invalid subcommand -- show the general usage and subcommand help
210 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
212 println!("{}\n", subcommand_help);
213 let exit_code = if args.is_empty() { 0 } else { 1 };
214 process::exit(exit_code);
218 // Some subcommands get extra options
219 match subcommand.as_str() {
221 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
222 opts.optmulti("", "test-args", "extra arguments", "ARGS");
226 "extra options to pass the compiler when running tests",
229 opts.optflag("", "no-doc", "do not run doc tests");
230 opts.optflag("", "doc", "only run doc tests");
231 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
235 "mode describing what file the actual ui output will be compared to",
241 "force {check,build,run}-pass tests to this mode.",
242 "check | build | run",
247 "enable this to generate a Rustfix coverage file, which is saved in \
248 `/<build_base>/rustfix_missing_coverage.txt`",
252 opts.optmulti("", "test-args", "extra arguments", "ARGS");
255 opts.optflag("", "open", "open the docs in a browser");
258 opts.optflag("", "all", "clean all build artifacts");
261 opts.optflag("", "check", "check formatting instead of applying.");
266 // Done specifying what options are possible, so do the getopts parsing
267 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
268 // Invalid argument/option format
269 println!("\n{}\n", e);
270 usage(1, &opts, &subcommand_help, &extra_help);
272 // Extra sanity check to make sure we didn't hit this crazy corner case:
274 // ./x.py --frobulate clean build
275 // ^-- option ^ ^- actual subcommand
276 // \_ arg to option could be mistaken as subcommand
277 let mut pass_sanity_check = true;
278 match matches.free.get(0) {
279 Some(check_subcommand) => {
280 if check_subcommand != subcommand {
281 pass_sanity_check = false;
285 pass_sanity_check = false;
288 if !pass_sanity_check {
289 println!("{}\n", subcommand_help);
291 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
292 You may need to move some options to after the subcommand.\n"
296 // Extra help text for some commands
297 match subcommand.as_str() {
299 subcommand_help.push_str(
302 This subcommand accepts a number of paths to directories to the crates
303 and/or artifacts to compile. For example:
305 ./x.py build library/core
306 ./x.py build library/core library/proc_macro
307 ./x.py build library/std --stage 1
309 If no arguments are passed then the complete artifacts for that stage are
313 ./x.py build --stage 1
315 For a quick build of a usable compiler, you can pass:
317 ./x.py build --stage 1 library/test
319 This will first build everything once (like `--stage 0` without further
320 arguments would), and then use the compiler built in stage 0 to build
321 library/test and its dependencies.
322 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
326 subcommand_help.push_str(
329 This subcommand accepts a number of paths to directories to the crates
330 and/or artifacts to compile. For example:
332 ./x.py check library/core
333 ./x.py check library/core library/proc_macro
335 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
336 also that since we use `cargo check`, by default this will automatically enable incremental
337 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
338 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
343 subcommand_help.push_str(
346 This subcommand accepts a number of paths to directories to the crates
347 and/or artifacts to run clippy against. For example:
349 ./x.py clippy library/core
350 ./x.py clippy library/core library/proc_macro",
354 subcommand_help.push_str(
357 This subcommand accepts a number of paths to directories to the crates
358 and/or artifacts to run `cargo fix` against. For example:
360 ./x.py fix library/core
361 ./x.py fix library/core library/proc_macro",
365 subcommand_help.push_str(
368 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
369 fails if it is not. For example:
376 subcommand_help.push_str(
379 This subcommand accepts a number of paths to test directories that
380 should be compiled and run. For example:
382 ./x.py test src/test/ui
383 ./x.py test library/std --test-args hash_map
384 ./x.py test library/std --stage 0 --no-doc
385 ./x.py test src/test/ui --bless
386 ./x.py test src/test/ui --compare-mode nll
388 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
389 just like `build library/std --stage N` it tests the compiler produced by the previous
392 Execute tool tests with a tool name argument:
396 If no arguments are passed then the complete artifacts for that stage are
400 ./x.py test --stage 1",
404 subcommand_help.push_str(
407 This subcommand accepts a number of paths to directories of documentation
408 to build. For example:
410 ./x.py doc src/doc/book
411 ./x.py doc src/doc/nomicon
412 ./x.py doc src/doc/book library/std
413 ./x.py doc library/std --open
415 If no arguments are passed then everything is documented:
418 ./x.py doc --stage 1",
422 subcommand_help.push_str(
425 This subcommand accepts a number of paths to tools to build and run. For
428 ./x.py run src/tools/expand-yaml-anchors
430 At least a tool needs to be called.",
435 // Get any optional paths which occur after the subcommand
436 let paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
438 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
440 // All subcommands except `clean` can have an optional "Available paths" section
441 if matches.opt_present("verbose") {
442 let config = Config::parse(&["build".to_string()]);
443 let build = Build::new(config);
445 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
446 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
447 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
449 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
454 // User passed in -h/--help?
455 if matches.opt_present("help") {
456 usage(0, &opts, &subcommand_help, &extra_help);
459 let cmd = match subcommand.as_str() {
460 "build" | "b" => Subcommand::Build { paths },
461 "check" | "c" => Subcommand::Check { paths },
462 "clippy" => Subcommand::Clippy { paths },
463 "fix" => Subcommand::Fix { paths },
464 "test" | "t" => Subcommand::Test {
466 bless: matches.opt_present("bless"),
467 compare_mode: matches.opt_str("compare-mode"),
468 pass: matches.opt_str("pass"),
469 test_args: matches.opt_strs("test-args"),
470 rustc_args: matches.opt_strs("rustc-args"),
471 fail_fast: !matches.opt_present("no-fail-fast"),
472 rustfix_coverage: matches.opt_present("rustfix-coverage"),
473 doc_tests: if matches.opt_present("doc") {
475 } else if matches.opt_present("no-doc") {
481 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
482 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
484 if !paths.is_empty() {
485 println!("\nclean does not take a path argument\n");
486 usage(1, &opts, &subcommand_help, &extra_help);
489 Subcommand::Clean { all: matches.opt_present("all") }
491 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
492 "dist" => Subcommand::Dist { paths },
493 "install" => Subcommand::Install { paths },
495 if paths.is_empty() {
496 println!("\nrun requires at least a path!\n");
497 usage(1, &opts, &subcommand_help, &extra_help);
499 Subcommand::Run { paths }
502 usage(1, &opts, &subcommand_help, &extra_help);
506 if let Subcommand::Check { .. } = &cmd {
507 if matches.opt_str("stage").is_some() {
508 println!("--stage not supported for x.py check, always treated as stage 0");
511 if matches.opt_str("keep-stage").is_some() {
512 println!("--keep-stage not supported for x.py check, only one stage available");
518 verbose: matches.opt_count("verbose"),
519 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
520 dry_run: matches.opt_present("dry-run"),
521 on_fail: matches.opt_str("on-fail"),
522 rustc_error_format: matches.opt_str("error-format"),
523 json_output: matches.opt_present("json-output"),
525 .opt_strs("keep-stage")
527 .map(|j| j.parse().expect("`keep-stage` should be a number"))
529 host: split(&matches.opt_strs("host"))
531 .map(|x| TargetSelection::from_user(&x))
532 .collect::<Vec<_>>(),
533 target: split(&matches.opt_strs("target"))
535 .map(|x| TargetSelection::from_user(&x))
536 .collect::<Vec<_>>(),
538 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
540 incremental: matches.opt_present("incremental"),
541 exclude: split(&matches.opt_strs("exclude"))
544 .collect::<Vec<_>>(),
545 deny_warnings: parse_deny_warnings(&matches),
546 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
547 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
554 pub fn test_args(&self) -> Vec<&str> {
556 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
557 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
563 pub fn rustc_args(&self) -> Vec<&str> {
565 Subcommand::Test { ref rustc_args, .. } => {
566 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
572 pub fn fail_fast(&self) -> bool {
574 Subcommand::Test { fail_fast, .. } => fail_fast,
579 pub fn doc_tests(&self) -> DocTests {
581 Subcommand::Test { doc_tests, .. } => doc_tests,
586 pub fn bless(&self) -> bool {
588 Subcommand::Test { bless, .. } => bless,
593 pub fn rustfix_coverage(&self) -> bool {
595 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
600 pub fn compare_mode(&self) -> Option<&str> {
602 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
607 pub fn pass(&self) -> Option<&str> {
609 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
614 pub fn open(&self) -> bool {
616 Subcommand::Doc { open, .. } => open,
622 fn split(s: &[String]) -> Vec<String> {
623 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
626 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
627 match matches.opt_str("warnings").as_deref() {
628 Some("deny") => Some(true),
629 Some("warn") => Some(false),
631 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);