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::cache::{Interned, INTERNER};
14 use crate::config::Config;
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>,
24 pub host: Vec<Interned<String>>,
25 pub target: Vec<Interned<String>>,
26 pub config: Option<PathBuf>,
27 pub jobs: Option<u32>,
29 pub incremental: bool,
30 pub exclude: Vec<PathBuf>,
31 pub rustc_error_format: Option<String>,
32 pub json_output: bool,
35 // This overrides the deny-warnings configuration option,
36 // which passes -Dwarnings to the compiler invocations.
38 // true => deny, false => warn
39 pub deny_warnings: Option<bool>,
41 pub llvm_skip_rebuild: Option<bool>,
66 /// Whether to automatically update stderr/stdout files
68 compare_mode: Option<String>,
70 test_args: Vec<String>,
71 rustc_args: Vec<String>,
74 rustfix_coverage: bool,
78 test_args: Vec<String>,
94 impl Default for Subcommand {
95 fn default() -> Subcommand {
96 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
101 pub fn parse(args: &[String]) -> Flags {
102 let mut extra_help = String::new();
103 let mut subcommand_help = String::from(
105 Usage: x.py <subcommand> [options] [<paths>...]
108 build, b Compile either the compiler or libraries
109 check, c Compile either the compiler or libraries, using cargo check
110 clippy Run clippy (uses rustup/cargo-installed clippy binary)
113 test, t Build and run some test suites
114 bench Build and run some benchmarks
115 doc Build documentation
116 clean Clean out build directories
117 dist Build distribution artifacts
118 install Install distribution artifacts
119 run, r Run tools contained in this repository
121 To learn more about a subcommand, run `./x.py <subcommand> -h`",
124 let mut opts = Options::new();
125 // Options common to all subcommands
126 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
127 opts.optflag("i", "incremental", "use incremental compilation");
128 opts.optopt("", "config", "TOML configuration file for build", "FILE");
129 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
130 opts.optmulti("", "host", "host targets to build", "HOST");
131 opts.optmulti("", "target", "target targets to build", "TARGET");
132 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
133 opts.optopt("", "on-fail", "command to run on failure", "CMD");
134 opts.optflag("", "dry-run", "dry run; don't build anything");
138 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
139 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
145 "stage(s) to keep without recompiling \
146 (pass multiple times to keep e.g., both stages 0 and 1)",
149 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
151 "number of jobs to run in parallel; \
152 defaults to {} (this host's logical CPU count)",
155 opts.optopt("j", "jobs", &j_msg, "JOBS");
156 opts.optflag("h", "help", "print this help message");
160 "if value is deny, will deny warnings, otherwise use default",
163 opts.optopt("", "error-format", "rustc error format", "FORMAT");
164 opts.optflag("", "json-output", "use message-format=json");
168 "whether rebuilding llvm should be skipped \
169 a VALUE of TRUE indicates that llvm will not be rebuilt \
170 VALUE overrides the skip-rebuild option in config.toml.",
176 |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
177 println!("{}", opts.usage(subcommand_help));
178 if !extra_help.is_empty() {
179 println!("{}", extra_help);
181 process::exit(exit_code);
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| {
207 let subcommand = match subcommand {
210 // No or an invalid subcommand -- show the general usage and subcommand help
211 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
213 println!("{}\n", subcommand_help);
214 let exit_code = if args.is_empty() { 0 } else { 1 };
215 process::exit(exit_code);
219 // Some subcommands get extra options
220 match subcommand.as_str() {
222 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
223 opts.optmulti("", "test-args", "extra arguments", "ARGS");
227 "extra options to pass the compiler when running tests",
230 opts.optflag("", "no-doc", "do not run doc tests");
231 opts.optflag("", "doc", "only run doc tests");
232 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
236 "mode describing what file the actual ui output will be compared to",
242 "force {check,build,run}-pass tests to this mode.",
243 "check | build | run",
248 "enable this to generate a Rustfix coverage file, which is saved in \
249 `/<build_base>/rustfix_missing_coverage.txt`",
253 opts.optmulti("", "test-args", "extra arguments", "ARGS");
256 opts.optflag("", "open", "open the docs in a browser");
259 opts.optflag("", "all", "clean all build artifacts");
262 opts.optflag("", "check", "check formatting instead of applying.");
267 // Done specifying what options are possible, so do the getopts parsing
268 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
269 // Invalid argument/option format
270 println!("\n{}\n", e);
271 usage(1, &opts, &subcommand_help, &extra_help);
273 // Extra sanity check to make sure we didn't hit this crazy corner case:
275 // ./x.py --frobulate clean build
276 // ^-- option ^ ^- actual subcommand
277 // \_ arg to option could be mistaken as subcommand
278 let mut pass_sanity_check = true;
279 match matches.free.get(0) {
280 Some(check_subcommand) => {
281 if check_subcommand != subcommand {
282 pass_sanity_check = false;
286 pass_sanity_check = false;
289 if !pass_sanity_check {
290 println!("{}\n", subcommand_help);
292 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
293 You may need to move some options to after the subcommand.\n"
297 // Extra help text for some commands
298 match subcommand.as_str() {
300 subcommand_help.push_str(
303 This subcommand accepts a number of paths to directories to the crates
304 and/or artifacts to compile. For example:
306 ./x.py build src/libcore
307 ./x.py build src/libcore src/libproc_macro
308 ./x.py build src/libstd --stage 1
310 If no arguments are passed then the complete artifacts for that stage are
314 ./x.py build --stage 1
316 For a quick build of a usable compiler, you can pass:
318 ./x.py build --stage 1 src/libtest
320 This will first build everything once (like `--stage 0` without further
321 arguments would), and then use the compiler built in stage 0 to build
322 src/libtest and its dependencies.
323 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
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 check src/libcore
334 ./x.py check src/libcore src/libproc_macro
336 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
337 also that since we use `cargo check`, by default this will automatically enable incremental
338 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
339 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
344 subcommand_help.push_str(
347 This subcommand accepts a number of paths to directories to the crates
348 and/or artifacts to run clippy against. For example:
350 ./x.py clippy src/libcore
351 ./x.py clippy src/libcore src/libproc_macro",
355 subcommand_help.push_str(
358 This subcommand accepts a number of paths to directories to the crates
359 and/or artifacts to run `cargo fix` against. For example:
361 ./x.py fix src/libcore
362 ./x.py fix src/libcore src/libproc_macro",
366 subcommand_help.push_str(
369 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
370 fails if it is not. For example:
377 subcommand_help.push_str(
380 This subcommand accepts a number of paths to test directories that
381 should be compiled and run. For example:
383 ./x.py test src/test/ui
384 ./x.py test src/libstd --test-args hash_map
385 ./x.py test src/libstd --stage 0 --no-doc
386 ./x.py test src/test/ui --bless
387 ./x.py test src/test/ui --compare-mode nll
389 Note that `test src/test/* --stage N` does NOT depend on `build src/rustc --stage N`;
390 just like `build src/libstd --stage N` it tests the compiler produced by the previous
393 Execute tool tests with a tool name argument:
397 If no arguments are passed then the complete artifacts for that stage are
401 ./x.py test --stage 1",
405 subcommand_help.push_str(
408 This subcommand accepts a number of paths to directories of documentation
409 to build. For example:
411 ./x.py doc src/doc/book
412 ./x.py doc src/doc/nomicon
413 ./x.py doc src/doc/book src/libstd
414 ./x.py doc src/libstd --open
416 If no arguments are passed then everything is documented:
419 ./x.py doc --stage 1",
423 subcommand_help.push_str(
426 This subcommand accepts a number of paths to tools to build and run. For
429 ./x.py run src/tool/expand-yaml-anchors
431 At least a tool needs to be called.",
436 // Get any optional paths which occur after the subcommand
437 let paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
439 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
441 // All subcommands except `clean` can have an optional "Available paths" section
442 if matches.opt_present("verbose") {
443 let config = Config::parse(&["build".to_string()]);
444 let build = Build::new(config);
446 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
447 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
448 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
450 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
455 // User passed in -h/--help?
456 if matches.opt_present("help") {
457 usage(0, &opts, &subcommand_help, &extra_help);
460 let cmd = match subcommand.as_str() {
461 "build" | "b" => Subcommand::Build { paths },
462 "check" | "c" => Subcommand::Check { paths },
463 "clippy" => Subcommand::Clippy { paths },
464 "fix" => Subcommand::Fix { paths },
465 "test" | "t" => Subcommand::Test {
467 bless: matches.opt_present("bless"),
468 compare_mode: matches.opt_str("compare-mode"),
469 pass: matches.opt_str("pass"),
470 test_args: matches.opt_strs("test-args"),
471 rustc_args: matches.opt_strs("rustc-args"),
472 fail_fast: !matches.opt_present("no-fail-fast"),
473 rustfix_coverage: matches.opt_present("rustfix-coverage"),
474 doc_tests: if matches.opt_present("doc") {
476 } else if matches.opt_present("no-doc") {
482 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
483 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
485 if !paths.is_empty() {
486 println!("\nclean does not take a path argument\n");
487 usage(1, &opts, &subcommand_help, &extra_help);
490 Subcommand::Clean { all: matches.opt_present("all") }
492 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
493 "dist" => Subcommand::Dist { paths },
494 "install" => Subcommand::Install { paths },
496 if paths.is_empty() {
497 println!("\nrun requires at least a path!\n");
498 usage(1, &opts, &subcommand_help, &extra_help);
500 Subcommand::Run { paths }
503 usage(1, &opts, &subcommand_help, &extra_help);
507 if let Subcommand::Check { .. } = &cmd {
508 if matches.opt_str("stage").is_some() {
509 println!("{}", "--stage not supported for x.py check, always treated as stage 0");
512 if matches.opt_str("keep-stage").is_some() {
515 "--keep-stage not supported for x.py check, only one stage available"
522 verbose: matches.opt_count("verbose"),
523 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
524 dry_run: matches.opt_present("dry-run"),
525 on_fail: matches.opt_str("on-fail"),
526 rustc_error_format: matches.opt_str("error-format"),
527 json_output: matches.opt_present("json-output"),
529 .opt_strs("keep-stage")
531 .map(|j| j.parse().expect("`keep-stage` should be a number"))
533 host: split(&matches.opt_strs("host"))
535 .map(|x| INTERNER.intern_string(x))
536 .collect::<Vec<_>>(),
537 target: split(&matches.opt_strs("target"))
539 .map(|x| INTERNER.intern_string(x))
540 .collect::<Vec<_>>(),
542 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
544 incremental: matches.opt_present("incremental"),
545 exclude: split(&matches.opt_strs("exclude"))
548 .collect::<Vec<_>>(),
549 deny_warnings: parse_deny_warnings(&matches),
550 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
551 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
558 pub fn test_args(&self) -> Vec<&str> {
560 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
561 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
567 pub fn rustc_args(&self) -> Vec<&str> {
569 Subcommand::Test { ref rustc_args, .. } => {
570 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
576 pub fn fail_fast(&self) -> bool {
578 Subcommand::Test { fail_fast, .. } => fail_fast,
583 pub fn doc_tests(&self) -> DocTests {
585 Subcommand::Test { doc_tests, .. } => doc_tests,
590 pub fn bless(&self) -> bool {
592 Subcommand::Test { bless, .. } => bless,
597 pub fn rustfix_coverage(&self) -> bool {
599 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
604 pub fn compare_mode(&self) -> Option<&str> {
606 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
611 pub fn pass(&self) -> Option<&str> {
613 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
618 pub fn open(&self) -> bool {
620 Subcommand::Doc { open, .. } => open,
626 fn split(s: &[String]) -> Vec<String> {
627 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
630 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
631 match matches.opt_str("warnings").as_deref() {
632 Some("deny") => Some(true),
633 Some("warn") => Some(false),
635 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);