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>,
24 pub host: Option<Vec<TargetSelection>>,
25 pub target: Option<Vec<TargetSelection>>,
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>,
97 impl Default for Subcommand {
98 fn default() -> Subcommand {
99 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
104 pub fn parse(args: &[String]) -> Flags {
105 let mut subcommand_help = String::from(
107 Usage: x.py <subcommand> [options] [<paths>...]
110 build, b Compile either the compiler or libraries
111 check, c Compile either the compiler or libraries, using cargo check
112 clippy Run clippy (uses rustup/cargo-installed clippy binary)
115 test, t Build and run some test suites
116 bench Build and run some benchmarks
117 doc Build documentation
118 clean Clean out build directories
119 dist Build distribution artifacts
120 install Install distribution artifacts
121 run, r Run tools contained in this repository
123 To learn more about a subcommand, run `./x.py <subcommand> -h`",
126 let mut opts = Options::new();
127 // Options common to all subcommands
128 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
129 opts.optflag("i", "incremental", "use incremental compilation");
130 opts.optopt("", "config", "TOML configuration file for build", "FILE");
131 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
132 opts.optmulti("", "host", "host targets to build", "HOST");
133 opts.optmulti("", "target", "target targets to build", "TARGET");
134 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
135 opts.optopt("", "on-fail", "command to run on failure", "CMD");
136 opts.optflag("", "dry-run", "dry run; don't build anything");
140 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
141 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
147 "stage(s) to keep without recompiling \
148 (pass multiple times to keep e.g., both stages 0 and 1)",
151 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
153 "number of jobs to run in parallel; \
154 defaults to {} (this host's logical CPU count)",
157 opts.optopt("j", "jobs", &j_msg, "JOBS");
158 opts.optflag("h", "help", "print this help message");
162 "if value is deny, will deny warnings, otherwise use default",
165 opts.optopt("", "error-format", "rustc error format", "FORMAT");
166 opts.optflag("", "json-output", "use message-format=json");
170 "whether rebuilding llvm should be skipped \
171 a VALUE of TRUE indicates that llvm will not be rebuilt \
172 VALUE overrides the skip-rebuild option in config.toml.",
176 // We can't use getopt to parse the options until we have completed specifying which
177 // options are valid, but under the current implementation, some options are conditional on
178 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
179 // complete the definition of the options. Then we can use the getopt::Matches object from
181 let subcommand = args.iter().find(|&s| {
200 let subcommand = match subcommand {
203 // No or an invalid subcommand -- show the general usage and subcommand help
204 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
206 println!("{}\n", subcommand_help);
207 let exit_code = if args.is_empty() { 0 } else { 1 };
208 process::exit(exit_code);
212 // Some subcommands get extra options
213 match subcommand.as_str() {
215 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
216 opts.optmulti("", "test-args", "extra arguments", "ARGS");
220 "extra options to pass the compiler when running tests",
223 opts.optflag("", "no-doc", "do not run doc tests");
224 opts.optflag("", "doc", "only run doc tests");
225 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
229 "mode describing what file the actual ui output will be compared to",
235 "force {check,build,run}-pass tests to this mode.",
236 "check | build | run",
241 "enable this to generate a Rustfix coverage file, which is saved in \
242 `/<build_base>/rustfix_missing_coverage.txt`",
246 opts.optmulti("", "test-args", "extra arguments", "ARGS");
249 opts.optflag("", "open", "open the docs in a browser");
252 opts.optflag("", "all", "clean all build artifacts");
255 opts.optflag("", "check", "check formatting instead of applying.");
261 let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
262 let mut extra_help = String::new();
264 // All subcommands except `clean` can have an optional "Available paths" section
266 let config = Config::parse(&["build".to_string()]);
267 let build = Build::new(config);
269 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
270 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
271 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
273 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
278 println!("{}", opts.usage(subcommand_help));
279 if !extra_help.is_empty() {
280 println!("{}", extra_help);
282 process::exit(exit_code);
285 // Done specifying what options are possible, so do the getopts parsing
286 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
287 // Invalid argument/option format
288 println!("\n{}\n", e);
289 usage(1, &opts, false, &subcommand_help);
292 // Extra sanity check to make sure we didn't hit this crazy corner case:
294 // ./x.py --frobulate clean build
295 // ^-- option ^ ^- actual subcommand
296 // \_ arg to option could be mistaken as subcommand
297 let mut pass_sanity_check = true;
298 match matches.free.get(0) {
299 Some(check_subcommand) => {
300 if check_subcommand != subcommand {
301 pass_sanity_check = false;
305 pass_sanity_check = false;
308 if !pass_sanity_check {
309 println!("{}\n", subcommand_help);
311 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
312 You may need to move some options to after the subcommand.\n"
316 // Extra help text for some commands
317 match subcommand.as_str() {
319 subcommand_help.push_str(
322 This subcommand accepts a number of paths to directories to the crates
323 and/or artifacts to compile. For example:
325 ./x.py build library/core
326 ./x.py build library/core library/proc_macro
327 ./x.py build library/std --stage 1
329 If no arguments are passed then the complete artifacts for that stage are
333 ./x.py build --stage 1
335 For a quick build of a usable compiler, you can pass:
337 ./x.py build --stage 1 library/test
339 This will first build everything once (like `--stage 0` without further
340 arguments would), and then use the compiler built in stage 0 to build
341 library/test and its dependencies.
342 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
346 subcommand_help.push_str(
349 This subcommand accepts a number of paths to directories to the crates
350 and/or artifacts to compile. For example:
352 ./x.py check library/core
353 ./x.py check library/core library/proc_macro
355 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
356 also that since we use `cargo check`, by default this will automatically enable incremental
357 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
358 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
363 subcommand_help.push_str(
366 This subcommand accepts a number of paths to directories to the crates
367 and/or artifacts to run clippy against. For example:
369 ./x.py clippy library/core
370 ./x.py clippy library/core library/proc_macro",
374 subcommand_help.push_str(
377 This subcommand accepts a number of paths to directories to the crates
378 and/or artifacts to run `cargo fix` against. For example:
380 ./x.py fix library/core
381 ./x.py fix library/core library/proc_macro",
385 subcommand_help.push_str(
388 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
389 fails if it is not. For example:
396 subcommand_help.push_str(
399 This subcommand accepts a number of paths to test directories that
400 should be compiled and run. For example:
402 ./x.py test src/test/ui
403 ./x.py test library/std --test-args hash_map
404 ./x.py test library/std --stage 0 --no-doc
405 ./x.py test src/test/ui --bless
406 ./x.py test src/test/ui --compare-mode nll
408 Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
409 just like `build library/std --stage N` it tests the compiler produced by the previous
412 Execute tool tests with a tool name argument:
416 If no arguments are passed then the complete artifacts for that stage are
420 ./x.py test --stage 1",
424 subcommand_help.push_str(
427 This subcommand accepts a number of paths to directories of documentation
428 to build. For example:
430 ./x.py doc src/doc/book
431 ./x.py doc src/doc/nomicon
432 ./x.py doc src/doc/book library/std
433 ./x.py doc library/std --open
435 If no arguments are passed then everything is documented:
438 ./x.py doc --stage 1",
442 subcommand_help.push_str(
445 This subcommand accepts a number of paths to tools to build and run. For
448 ./x.py run src/tools/expand-yaml-anchors
450 At least a tool needs to be called.",
454 subcommand_help.push_str(
457 This subcommand accepts a 'profile' to use for builds. For example:
461 The profile is optional and you will be prompted interactively if it is not given.",
466 // Get any optional paths which occur after the subcommand
467 let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
469 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
470 let verbose = matches.opt_present("verbose");
472 // User passed in -h/--help?
473 if matches.opt_present("help") {
474 usage(0, &opts, verbose, &subcommand_help);
477 let cmd = match subcommand.as_str() {
478 "build" | "b" => Subcommand::Build { paths },
479 "check" | "c" => Subcommand::Check { paths },
480 "clippy" => Subcommand::Clippy { paths },
481 "fix" => Subcommand::Fix { paths },
482 "test" | "t" => Subcommand::Test {
484 bless: matches.opt_present("bless"),
485 compare_mode: matches.opt_str("compare-mode"),
486 pass: matches.opt_str("pass"),
487 test_args: matches.opt_strs("test-args"),
488 rustc_args: matches.opt_strs("rustc-args"),
489 fail_fast: !matches.opt_present("no-fail-fast"),
490 rustfix_coverage: matches.opt_present("rustfix-coverage"),
491 doc_tests: if matches.opt_present("doc") {
493 } else if matches.opt_present("no-doc") {
499 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
500 "doc" => Subcommand::Doc { paths, open: matches.opt_present("open") },
502 if !paths.is_empty() {
503 println!("\nclean does not take a path argument\n");
504 usage(1, &opts, verbose, &subcommand_help);
507 Subcommand::Clean { all: matches.opt_present("all") }
509 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
510 "dist" => Subcommand::Dist { paths },
511 "install" => Subcommand::Install { paths },
513 if paths.is_empty() {
514 println!("\nrun requires at least a path!\n");
515 usage(1, &opts, verbose, &subcommand_help);
517 Subcommand::Run { paths }
520 let path = if paths.len() > 1 {
521 println!("\nat most one profile can be passed to setup\n");
522 usage(1, &opts, verbose, &subcommand_help)
523 } else if let Some(path) = paths.pop() {
524 t!(path.into_os_string().into_string().map_err(|path| format!(
525 "{} is not a valid UTF8 string",
526 path.to_string_lossy()
529 t!(crate::setup::interactive_path())
531 Subcommand::Setup { path }
534 usage(1, &opts, verbose, &subcommand_help);
538 if let Subcommand::Check { .. } = &cmd {
539 if matches.opt_str("stage").is_some() {
540 println!("--stage not supported for x.py check, always treated as stage 0");
543 if matches.opt_str("keep-stage").is_some() {
544 println!("--keep-stage not supported for x.py check, only one stage available");
550 verbose: matches.opt_count("verbose"),
551 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
552 dry_run: matches.opt_present("dry-run"),
553 on_fail: matches.opt_str("on-fail"),
554 rustc_error_format: matches.opt_str("error-format"),
555 json_output: matches.opt_present("json-output"),
557 .opt_strs("keep-stage")
559 .map(|j| j.parse().expect("`keep-stage` should be a number"))
561 host: if matches.opt_present("host") {
563 split(&matches.opt_strs("host"))
565 .map(|x| TargetSelection::from_user(&x))
566 .collect::<Vec<_>>(),
571 target: if matches.opt_present("target") {
573 split(&matches.opt_strs("target"))
575 .map(|x| TargetSelection::from_user(&x))
576 .collect::<Vec<_>>(),
582 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
584 incremental: matches.opt_present("incremental"),
585 exclude: split(&matches.opt_strs("exclude"))
588 .collect::<Vec<_>>(),
589 deny_warnings: parse_deny_warnings(&matches),
590 llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
591 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
598 pub fn test_args(&self) -> Vec<&str> {
600 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
601 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
607 pub fn rustc_args(&self) -> Vec<&str> {
609 Subcommand::Test { ref rustc_args, .. } => {
610 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
616 pub fn fail_fast(&self) -> bool {
618 Subcommand::Test { fail_fast, .. } => fail_fast,
623 pub fn doc_tests(&self) -> DocTests {
625 Subcommand::Test { doc_tests, .. } => doc_tests,
630 pub fn bless(&self) -> bool {
632 Subcommand::Test { bless, .. } => bless,
637 pub fn rustfix_coverage(&self) -> bool {
639 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
644 pub fn compare_mode(&self) -> Option<&str> {
646 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
651 pub fn pass(&self) -> Option<&str> {
653 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
658 pub fn open(&self) -> bool {
660 Subcommand::Doc { open, .. } => open,
666 fn split(s: &[String]) -> Vec<String> {
667 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
670 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
671 match matches.opt_str("warnings").as_deref() {
672 Some("deny") => Some(true),
673 Some("warn") => Some(false),
675 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);