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;
15 use crate::{Build, DocTests};
17 use crate::cache::{Interned, INTERNER};
19 /// Deserialized version of all flags for this compile.
21 pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
22 pub on_fail: Option<String>,
23 pub stage: Option<u32>,
24 pub keep_stage: Vec<u32>,
26 pub host: Vec<Interned<String>>,
27 pub target: Vec<Interned<String>>,
28 pub config: Option<PathBuf>,
29 pub jobs: Option<u32>,
31 pub incremental: bool,
32 pub exclude: Vec<PathBuf>,
33 pub rustc_error_format: Option<String>,
36 // This overrides the deny-warnings configuation option,
37 // which passes -Dwarnings to the compiler invocations.
39 // true => deny, false => warn
40 pub deny_warnings: Option<bool>,
64 /// Whether to automatically update stderr/stdout files
66 compare_mode: Option<String>,
68 test_args: Vec<String>,
69 rustc_args: Vec<String>,
72 rustfix_coverage: bool,
76 test_args: Vec<String>,
89 impl Default for Subcommand {
90 fn default() -> Subcommand {
91 Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
96 pub fn parse(args: &[String]) -> Flags {
97 let mut extra_help = String::new();
98 let mut subcommand_help = String::from(
100 Usage: x.py <subcommand> [options] [<paths>...]
103 build Compile either the compiler or libraries
104 check Compile either the compiler or libraries, using cargo check
108 test Build and run some test suites
109 bench Build and run some benchmarks
110 doc Build documentation
111 clean Clean out build directories
112 dist Build distribution artifacts
113 install Install distribution artifacts
115 To learn more about a subcommand, run `./x.py <subcommand> -h`",
118 let mut opts = Options::new();
119 // Options common to all subcommands
120 opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
121 opts.optflag("i", "incremental", "use incremental compilation");
122 opts.optopt("", "config", "TOML configuration file for build", "FILE");
123 opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
124 opts.optmulti("", "host", "host targets to build", "HOST");
125 opts.optmulti("", "target", "target targets to build", "TARGET");
126 opts.optmulti("", "exclude", "build paths to exclude", "PATH");
127 opts.optopt("", "on-fail", "command to run on failure", "CMD");
128 opts.optflag("", "dry-run", "dry run; don't build anything");
132 "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
133 bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
139 "stage(s) to keep without recompiling \
140 (pass multiple times to keep e.g., both stages 0 and 1)",
143 opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
144 opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS");
145 opts.optflag("h", "help", "print this help message");
149 "if value is deny, will deny warnings, otherwise use default",
152 opts.optopt("", "error-format", "rustc error format", "FORMAT");
156 |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
157 println!("{}", opts.usage(subcommand_help));
158 if !extra_help.is_empty() {
159 println!("{}", extra_help);
161 process::exit(exit_code);
164 // We can't use getopt to parse the options until we have completed specifying which
165 // options are valid, but under the current implementation, some options are conditional on
166 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
167 // complete the definition of the options. Then we can use the getopt::Matches object from
169 let subcommand = args.iter().find(|&s| {
182 let subcommand = match subcommand {
185 // No or an invalid subcommand -- show the general usage and subcommand help
186 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
188 println!("{}\n", subcommand_help);
189 let exit_code = if args.is_empty() { 0 } else { 1 };
190 process::exit(exit_code);
194 // Some subcommands get extra options
195 match subcommand.as_str() {
197 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
198 opts.optmulti("", "test-args", "extra arguments", "ARGS");
202 "extra options to pass the compiler when running tests",
205 opts.optflag("", "no-doc", "do not run doc tests");
206 opts.optflag("", "doc", "only run doc tests");
207 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
211 "mode describing what file the actual ui output will be compared to",
217 "force {check,build,run}-pass tests to this mode.",
218 "check | build | run",
223 "enable this to generate a Rustfix coverage file, which is saved in \
224 `/<build_base>/rustfix_missing_coverage.txt`",
228 opts.optmulti("", "test-args", "extra arguments", "ARGS");
231 opts.optflag("", "all", "clean all build artifacts");
234 opts.optflag("", "check", "check formatting instead of applying.");
239 // Done specifying what options are possible, so do the getopts parsing
240 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
241 // Invalid argument/option format
242 println!("\n{}\n", e);
243 usage(1, &opts, &subcommand_help, &extra_help);
245 // Extra sanity check to make sure we didn't hit this crazy corner case:
247 // ./x.py --frobulate clean build
248 // ^-- option ^ ^- actual subcommand
249 // \_ arg to option could be mistaken as subcommand
250 let mut pass_sanity_check = true;
251 match matches.free.get(0) {
252 Some(check_subcommand) => {
253 if check_subcommand != subcommand {
254 pass_sanity_check = false;
258 pass_sanity_check = false;
261 if !pass_sanity_check {
262 println!("{}\n", subcommand_help);
264 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
265 You may need to move some options to after the subcommand.\n"
269 // Extra help text for some commands
270 match subcommand.as_str() {
272 subcommand_help.push_str(
275 This subcommand accepts a number of paths to directories to the crates
276 and/or artifacts to compile. For example:
278 ./x.py build src/libcore
279 ./x.py build src/libcore src/libproc_macro
280 ./x.py build src/libstd --stage 1
282 If no arguments are passed then the complete artifacts for that stage are
286 ./x.py build --stage 1
288 For a quick build of a usable compiler, you can pass:
290 ./x.py build --stage 1 src/libtest
292 This will first build everything once (like `--stage 0` without further
293 arguments would), and then use the compiler built in stage 0 to build
294 src/libtest and its dependencies.
295 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
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 check src/libcore
306 ./x.py check src/libcore src/libproc_macro
308 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
309 also that since we use `cargo check`, by default this will automatically enable incremental
310 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
311 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
316 subcommand_help.push_str(
319 This subcommand accepts a number of paths to directories to the crates
320 and/or artifacts to run clippy against. For example:
322 ./x.py clippy src/libcore
323 ./x.py clippy src/libcore src/libproc_macro",
327 subcommand_help.push_str(
330 This subcommand accepts a number of paths to directories to the crates
331 and/or artifacts to run `cargo fix` against. For example:
333 ./x.py fix src/libcore
334 ./x.py fix src/libcore src/libproc_macro",
338 subcommand_help.push_str(
341 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
342 fails if it is not. For example:
349 subcommand_help.push_str(
352 This subcommand accepts a number of paths to directories to tests that
353 should be compiled and run. For example:
355 ./x.py test src/test/ui
356 ./x.py test src/libstd --test-args hash_map
357 ./x.py test src/libstd --stage 0 --no-doc
358 ./x.py test src/test/ui --bless
359 ./x.py test src/test/ui --compare-mode nll
361 Note that `test src/test/* --stage N` does NOT depend on `build src/rustc --stage N`;
362 just like `build src/libstd --stage N` it tests the compiler produced by the previous
365 If no arguments are passed then the complete artifacts for that stage are
369 ./x.py test --stage 1",
373 subcommand_help.push_str(
376 This subcommand accepts a number of paths to directories of documentation
377 to build. For example:
379 ./x.py doc src/doc/book
380 ./x.py doc src/doc/nomicon
381 ./x.py doc src/doc/book src/libstd
383 If no arguments are passed then everything is documented:
386 ./x.py doc --stage 1",
391 // Get any optional paths which occur after the subcommand
392 let paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
394 let cfg_file = matches.opt_str("config").map(PathBuf::from).or_else(|| {
395 if fs::metadata("config.toml").is_ok() {
396 Some(PathBuf::from("config.toml"))
402 // All subcommands except `clean` can have an optional "Available paths" section
403 if matches.opt_present("verbose") {
404 let config = Config::parse(&["build".to_string()]);
405 let mut build = Build::new(config);
406 metadata::build(&mut build);
408 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
409 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
410 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
412 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
417 // User passed in -h/--help?
418 if matches.opt_present("help") {
419 usage(0, &opts, &subcommand_help, &extra_help);
422 let cmd = match subcommand.as_str() {
423 "build" => Subcommand::Build { paths },
424 "check" => Subcommand::Check { paths },
425 "clippy" => Subcommand::Clippy { paths },
426 "fix" => Subcommand::Fix { paths },
427 "test" => Subcommand::Test {
429 bless: matches.opt_present("bless"),
430 compare_mode: matches.opt_str("compare-mode"),
431 pass: matches.opt_str("pass"),
432 test_args: matches.opt_strs("test-args"),
433 rustc_args: matches.opt_strs("rustc-args"),
434 fail_fast: !matches.opt_present("no-fail-fast"),
435 rustfix_coverage: matches.opt_present("rustfix-coverage"),
436 doc_tests: if matches.opt_present("doc") {
438 } else if matches.opt_present("no-doc") {
444 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
445 "doc" => Subcommand::Doc { paths },
447 if !paths.is_empty() {
448 println!("\nclean does not take a path argument\n");
449 usage(1, &opts, &subcommand_help, &extra_help);
452 Subcommand::Clean { all: matches.opt_present("all") }
454 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
455 "dist" => Subcommand::Dist { paths },
456 "install" => Subcommand::Install { paths },
458 usage(1, &opts, &subcommand_help, &extra_help);
463 verbose: matches.opt_count("verbose"),
464 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
465 dry_run: matches.opt_present("dry-run"),
466 on_fail: matches.opt_str("on-fail"),
467 rustc_error_format: matches.opt_str("error-format"),
469 .opt_strs("keep-stage")
471 .map(|j| j.parse().expect("`keep-stage` should be a number"))
473 host: split(&matches.opt_strs("host"))
475 .map(|x| INTERNER.intern_string(x))
476 .collect::<Vec<_>>(),
477 target: split(&matches.opt_strs("target"))
479 .map(|x| INTERNER.intern_string(x))
480 .collect::<Vec<_>>(),
482 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
484 incremental: matches.opt_present("incremental"),
485 exclude: split(&matches.opt_strs("exclude"))
488 .collect::<Vec<_>>(),
489 deny_warnings: parse_deny_warnings(&matches),
495 pub fn test_args(&self) -> Vec<&str> {
497 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
498 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
504 pub fn rustc_args(&self) -> Vec<&str> {
506 Subcommand::Test { ref rustc_args, .. } => {
507 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
513 pub fn fail_fast(&self) -> bool {
515 Subcommand::Test { fail_fast, .. } => fail_fast,
520 pub fn doc_tests(&self) -> DocTests {
522 Subcommand::Test { doc_tests, .. } => doc_tests,
527 pub fn bless(&self) -> bool {
529 Subcommand::Test { bless, .. } => bless,
534 pub fn rustfix_coverage(&self) -> bool {
536 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
541 pub fn compare_mode(&self) -> Option<&str> {
543 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
548 pub fn pass(&self) -> Option<&str> {
550 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
556 fn split(s: &[String]) -> Vec<String> {
557 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
560 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
561 match matches.opt_str("warnings").as_ref().map(|v| v.as_str()) {
562 Some("deny") => Some(true),
563 Some("warn") => Some(false),
565 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);