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 "whether rebuilding llvm should be skipped \
157 a VALUE of TRUE indicates that llvm will not be rebuilt \
158 VALUE overrides the skip-rebuild option in config.toml.",
164 |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
165 println!("{}", opts.usage(subcommand_help));
166 if !extra_help.is_empty() {
167 println!("{}", extra_help);
169 process::exit(exit_code);
172 // We can't use getopt to parse the options until we have completed specifying which
173 // options are valid, but under the current implementation, some options are conditional on
174 // the subcommand. Therefore we must manually identify the subcommand first, so that we can
175 // complete the definition of the options. Then we can use the getopt::Matches object from
177 let subcommand = args.iter().find(|&s| {
190 let subcommand = match subcommand {
193 // No or an invalid subcommand -- show the general usage and subcommand help
194 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
196 println!("{}\n", subcommand_help);
197 let exit_code = if args.is_empty() { 0 } else { 1 };
198 process::exit(exit_code);
202 // Some subcommands get extra options
203 match subcommand.as_str() {
205 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
206 opts.optmulti("", "test-args", "extra arguments", "ARGS");
210 "extra options to pass the compiler when running tests",
213 opts.optflag("", "no-doc", "do not run doc tests");
214 opts.optflag("", "doc", "only run doc tests");
215 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
219 "mode describing what file the actual ui output will be compared to",
225 "force {check,build,run}-pass tests to this mode.",
226 "check | build | run",
231 "enable this to generate a Rustfix coverage file, which is saved in \
232 `/<build_base>/rustfix_missing_coverage.txt`",
236 opts.optmulti("", "test-args", "extra arguments", "ARGS");
239 opts.optflag("", "all", "clean all build artifacts");
242 opts.optflag("", "check", "check formatting instead of applying.");
247 // Done specifying what options are possible, so do the getopts parsing
248 let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
249 // Invalid argument/option format
250 println!("\n{}\n", e);
251 usage(1, &opts, &subcommand_help, &extra_help);
253 // Extra sanity check to make sure we didn't hit this crazy corner case:
255 // ./x.py --frobulate clean build
256 // ^-- option ^ ^- actual subcommand
257 // \_ arg to option could be mistaken as subcommand
258 let mut pass_sanity_check = true;
259 match matches.free.get(0) {
260 Some(check_subcommand) => {
261 if check_subcommand != subcommand {
262 pass_sanity_check = false;
266 pass_sanity_check = false;
269 if !pass_sanity_check {
270 println!("{}\n", subcommand_help);
272 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
273 You may need to move some options to after the subcommand.\n"
277 // Extra help text for some commands
278 match subcommand.as_str() {
280 subcommand_help.push_str(
283 This subcommand accepts a number of paths to directories to the crates
284 and/or artifacts to compile. For example:
286 ./x.py build src/libcore
287 ./x.py build src/libcore src/libproc_macro
288 ./x.py build src/libstd --stage 1
290 If no arguments are passed then the complete artifacts for that stage are
294 ./x.py build --stage 1
296 For a quick build of a usable compiler, you can pass:
298 ./x.py build --stage 1 src/libtest
300 This will first build everything once (like `--stage 0` without further
301 arguments would), and then use the compiler built in stage 0 to build
302 src/libtest and its dependencies.
303 Once this is done, build/$ARCH/stage1 contains a usable compiler.",
307 subcommand_help.push_str(
310 This subcommand accepts a number of paths to directories to the crates
311 and/or artifacts to compile. For example:
313 ./x.py check src/libcore
314 ./x.py check src/libcore src/libproc_macro
316 If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
317 also that since we use `cargo check`, by default this will automatically enable incremental
318 compilation, so there's no need to pass it separately, though it won't hurt. We also completely
319 ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
324 subcommand_help.push_str(
327 This subcommand accepts a number of paths to directories to the crates
328 and/or artifacts to run clippy against. For example:
330 ./x.py clippy src/libcore
331 ./x.py clippy src/libcore src/libproc_macro",
335 subcommand_help.push_str(
338 This subcommand accepts a number of paths to directories to the crates
339 and/or artifacts to run `cargo fix` against. For example:
341 ./x.py fix src/libcore
342 ./x.py fix src/libcore src/libproc_macro",
346 subcommand_help.push_str(
349 This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
350 fails if it is not. For example:
357 subcommand_help.push_str(
360 This subcommand accepts a number of paths to directories to tests that
361 should be compiled and run. For example:
363 ./x.py test src/test/ui
364 ./x.py test src/libstd --test-args hash_map
365 ./x.py test src/libstd --stage 0 --no-doc
366 ./x.py test src/test/ui --bless
367 ./x.py test src/test/ui --compare-mode nll
369 Note that `test src/test/* --stage N` does NOT depend on `build src/rustc --stage N`;
370 just like `build src/libstd --stage N` it tests the compiler produced by the previous
373 If no arguments are passed then the complete artifacts for that stage are
377 ./x.py test --stage 1",
381 subcommand_help.push_str(
384 This subcommand accepts a number of paths to directories of documentation
385 to build. For example:
387 ./x.py doc src/doc/book
388 ./x.py doc src/doc/nomicon
389 ./x.py doc src/doc/book src/libstd
391 If no arguments are passed then everything is documented:
394 ./x.py doc --stage 1",
399 // Get any optional paths which occur after the subcommand
400 let paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
402 let cfg_file = matches.opt_str("config").map(PathBuf::from).or_else(|| {
403 if fs::metadata("config.toml").is_ok() {
404 Some(PathBuf::from("config.toml"))
410 // All subcommands except `clean` can have an optional "Available paths" section
411 if matches.opt_present("verbose") {
412 let config = Config::parse(&["build".to_string()]);
413 let mut build = Build::new(config);
414 metadata::build(&mut build);
416 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
417 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
418 } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
420 format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
425 // User passed in -h/--help?
426 if matches.opt_present("help") {
427 usage(0, &opts, &subcommand_help, &extra_help);
430 let cmd = match subcommand.as_str() {
431 "build" => Subcommand::Build { paths },
432 "check" => Subcommand::Check { paths },
433 "clippy" => Subcommand::Clippy { paths },
434 "fix" => Subcommand::Fix { paths },
435 "test" => Subcommand::Test {
437 bless: matches.opt_present("bless"),
438 compare_mode: matches.opt_str("compare-mode"),
439 pass: matches.opt_str("pass"),
440 test_args: matches.opt_strs("test-args"),
441 rustc_args: matches.opt_strs("rustc-args"),
442 fail_fast: !matches.opt_present("no-fail-fast"),
443 rustfix_coverage: matches.opt_present("rustfix-coverage"),
444 doc_tests: if matches.opt_present("doc") {
446 } else if matches.opt_present("no-doc") {
452 "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
453 "doc" => Subcommand::Doc { paths },
455 if !paths.is_empty() {
456 println!("\nclean does not take a path argument\n");
457 usage(1, &opts, &subcommand_help, &extra_help);
460 Subcommand::Clean { all: matches.opt_present("all") }
462 "fmt" => Subcommand::Format { check: matches.opt_present("check") },
463 "dist" => Subcommand::Dist { paths },
464 "install" => Subcommand::Install { paths },
466 usage(1, &opts, &subcommand_help, &extra_help);
471 verbose: matches.opt_count("verbose"),
472 stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
473 dry_run: matches.opt_present("dry-run"),
474 on_fail: matches.opt_str("on-fail"),
475 rustc_error_format: matches.opt_str("error-format"),
477 .opt_strs("keep-stage")
479 .map(|j| j.parse().expect("`keep-stage` should be a number"))
481 host: split(&matches.opt_strs("host"))
483 .map(|x| INTERNER.intern_string(x))
484 .collect::<Vec<_>>(),
485 target: split(&matches.opt_strs("target"))
487 .map(|x| INTERNER.intern_string(x))
488 .collect::<Vec<_>>(),
490 jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
492 incremental: matches.opt_present("incremental"),
493 exclude: split(&matches.opt_strs("exclude"))
496 .collect::<Vec<_>>(),
497 deny_warnings: parse_deny_warnings(&matches),
503 pub fn test_args(&self) -> Vec<&str> {
505 Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
506 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
512 pub fn rustc_args(&self) -> Vec<&str> {
514 Subcommand::Test { ref rustc_args, .. } => {
515 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
521 pub fn fail_fast(&self) -> bool {
523 Subcommand::Test { fail_fast, .. } => fail_fast,
528 pub fn doc_tests(&self) -> DocTests {
530 Subcommand::Test { doc_tests, .. } => doc_tests,
535 pub fn bless(&self) -> bool {
537 Subcommand::Test { bless, .. } => bless,
542 pub fn rustfix_coverage(&self) -> bool {
544 Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
549 pub fn compare_mode(&self) -> Option<&str> {
551 Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
556 pub fn pass(&self) -> Option<&str> {
558 Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
564 fn split(s: &[String]) -> Vec<String> {
565 s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
568 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
569 match matches.opt_str("warnings").as_ref().map(|v| v.as_str()) {
570 Some("deny") => Some(true),
571 Some("warn") => Some(false),
573 eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);