]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/flags.rs
Rollup merge of #88706 - ThePuzzlemaker:issue-88609, r=jackh726
[rust.git] / src / bootstrap / flags.rs
1 //! Command-line interface of the rustbuild build system.
2 //!
3 //! This module implements the command-line parsing of the build system which
4 //! has various flags to configure how it's run.
5
6 use std::env;
7 use std::path::PathBuf;
8 use std::process;
9
10 use build_helper::t;
11 use getopts::Options;
12
13 use crate::builder::Builder;
14 use crate::config::{Config, TargetSelection};
15 use crate::setup::Profile;
16 use crate::{Build, DocTests};
17
18 pub enum Color {
19     Always,
20     Never,
21     Auto,
22 }
23
24 impl Default for Color {
25     fn default() -> Self {
26         Self::Auto
27     }
28 }
29
30 impl std::str::FromStr for Color {
31     type Err = ();
32
33     fn from_str(s: &str) -> Result<Self, Self::Err> {
34         match s.to_lowercase().as_str() {
35             "always" => Ok(Self::Always),
36             "never" => Ok(Self::Never),
37             "auto" => Ok(Self::Auto),
38             _ => Err(()),
39         }
40     }
41 }
42
43 /// Deserialized version of all flags for this compile.
44 pub struct Flags {
45     pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
46     pub on_fail: Option<String>,
47     pub stage: Option<u32>,
48     pub keep_stage: Vec<u32>,
49     pub keep_stage_std: Vec<u32>,
50
51     pub host: Option<Vec<TargetSelection>>,
52     pub target: Option<Vec<TargetSelection>>,
53     pub config: Option<PathBuf>,
54     pub jobs: Option<u32>,
55     pub cmd: Subcommand,
56     pub incremental: bool,
57     pub exclude: Vec<PathBuf>,
58     pub include_default_paths: bool,
59     pub rustc_error_format: Option<String>,
60     pub json_output: bool,
61     pub dry_run: bool,
62     pub color: Color,
63
64     // This overrides the deny-warnings configuration option,
65     // which passes -Dwarnings to the compiler invocations.
66     //
67     // true => deny, false => warn
68     pub deny_warnings: Option<bool>,
69
70     pub llvm_skip_rebuild: Option<bool>,
71
72     pub rust_profile_use: Option<String>,
73     pub rust_profile_generate: Option<String>,
74
75     pub llvm_profile_use: Option<String>,
76     // LLVM doesn't support a custom location for generating profile
77     // information.
78     //
79     // llvm_out/build/profiles/ is the location this writes to.
80     pub llvm_profile_generate: bool,
81 }
82
83 pub enum Subcommand {
84     Build {
85         paths: Vec<PathBuf>,
86     },
87     Check {
88         paths: Vec<PathBuf>,
89     },
90     Clippy {
91         fix: bool,
92         paths: Vec<PathBuf>,
93     },
94     Fix {
95         paths: Vec<PathBuf>,
96     },
97     Format {
98         paths: Vec<PathBuf>,
99         check: bool,
100     },
101     Doc {
102         paths: Vec<PathBuf>,
103         open: bool,
104     },
105     Test {
106         paths: Vec<PathBuf>,
107         /// Whether to automatically update stderr/stdout files
108         bless: bool,
109         force_rerun: bool,
110         compare_mode: Option<String>,
111         pass: Option<String>,
112         run: Option<String>,
113         test_args: Vec<String>,
114         rustc_args: Vec<String>,
115         fail_fast: bool,
116         doc_tests: DocTests,
117         rustfix_coverage: bool,
118     },
119     Bench {
120         paths: Vec<PathBuf>,
121         test_args: Vec<String>,
122     },
123     Clean {
124         all: bool,
125     },
126     Dist {
127         paths: Vec<PathBuf>,
128     },
129     Install {
130         paths: Vec<PathBuf>,
131     },
132     Run {
133         paths: Vec<PathBuf>,
134     },
135     Setup {
136         profile: Profile,
137     },
138 }
139
140 impl Default for Subcommand {
141     fn default() -> Subcommand {
142         Subcommand::Build { paths: vec![PathBuf::from("nowhere")] }
143     }
144 }
145
146 impl Flags {
147     pub fn parse(args: &[String]) -> Flags {
148         let mut subcommand_help = String::from(
149             "\
150 Usage: x.py <subcommand> [options] [<paths>...]
151
152 Subcommands:
153     build, b    Compile either the compiler or libraries
154     check, c    Compile either the compiler or libraries, using cargo check
155     clippy      Run clippy (uses rustup/cargo-installed clippy binary)
156     fix         Run cargo fix
157     fmt         Run rustfmt
158     test, t     Build and run some test suites
159     bench       Build and run some benchmarks
160     doc, d      Build documentation
161     clean       Clean out build directories
162     dist        Build distribution artifacts
163     install     Install distribution artifacts
164     run, r      Run tools contained in this repository
165     setup       Create a config.toml (making it easier to use `x.py` itself)
166
167 To learn more about a subcommand, run `./x.py <subcommand> -h`",
168         );
169
170         let mut opts = Options::new();
171         // Options common to all subcommands
172         opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
173         opts.optflag("i", "incremental", "use incremental compilation");
174         opts.optopt("", "config", "TOML configuration file for build", "FILE");
175         opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
176         opts.optmulti("", "host", "host targets to build", "HOST");
177         opts.optmulti("", "target", "target targets to build", "TARGET");
178         opts.optmulti("", "exclude", "build paths to exclude", "PATH");
179         opts.optflag(
180             "",
181             "include-default-paths",
182             "include default paths in addition to the provided ones",
183         );
184         opts.optopt("", "on-fail", "command to run on failure", "CMD");
185         opts.optflag("", "dry-run", "dry run; don't build anything");
186         opts.optopt(
187             "",
188             "stage",
189             "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
190              bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
191             "N",
192         );
193         opts.optmulti(
194             "",
195             "keep-stage",
196             "stage(s) to keep without recompiling \
197             (pass multiple times to keep e.g., both stages 0 and 1)",
198             "N",
199         );
200         opts.optmulti(
201             "",
202             "keep-stage-std",
203             "stage(s) of the standard library to keep without recompiling \
204             (pass multiple times to keep e.g., both stages 0 and 1)",
205             "N",
206         );
207         opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
208         let j_msg = format!(
209             "number of jobs to run in parallel; \
210              defaults to {} (this host's logical CPU count)",
211             num_cpus::get()
212         );
213         opts.optopt("j", "jobs", &j_msg, "JOBS");
214         opts.optflag("h", "help", "print this help message");
215         opts.optopt(
216             "",
217             "warnings",
218             "if value is deny, will deny warnings, otherwise use default",
219             "VALUE",
220         );
221         opts.optopt("", "error-format", "rustc error format", "FORMAT");
222         opts.optflag("", "json-output", "use message-format=json");
223         opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE");
224         opts.optopt(
225             "",
226             "llvm-skip-rebuild",
227             "whether rebuilding llvm should be skipped \
228              a VALUE of TRUE indicates that llvm will not be rebuilt \
229              VALUE overrides the skip-rebuild option in config.toml.",
230             "VALUE",
231         );
232         opts.optopt(
233             "",
234             "rust-profile-generate",
235             "generate PGO profile with rustc build",
236             "PROFILE",
237         );
238         opts.optopt("", "rust-profile-use", "use PGO profile for rustc build", "PROFILE");
239         opts.optflag("", "llvm-profile-generate", "generate PGO profile with llvm built for rustc");
240         opts.optopt("", "llvm-profile-use", "use PGO profile for llvm build", "PROFILE");
241
242         // We can't use getopt to parse the options until we have completed specifying which
243         // options are valid, but under the current implementation, some options are conditional on
244         // the subcommand. Therefore we must manually identify the subcommand first, so that we can
245         // complete the definition of the options.  Then we can use the getopt::Matches object from
246         // there on out.
247         let subcommand = args.iter().find(|&s| {
248             (s == "build")
249                 || (s == "b")
250                 || (s == "check")
251                 || (s == "c")
252                 || (s == "clippy")
253                 || (s == "fix")
254                 || (s == "fmt")
255                 || (s == "test")
256                 || (s == "t")
257                 || (s == "bench")
258                 || (s == "doc")
259                 || (s == "d")
260                 || (s == "clean")
261                 || (s == "dist")
262                 || (s == "install")
263                 || (s == "run")
264                 || (s == "r")
265                 || (s == "setup")
266         });
267         let subcommand = match subcommand {
268             Some(s) => s,
269             None => {
270                 // No or an invalid subcommand -- show the general usage and subcommand help
271                 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
272                 // subcommand.
273                 println!("{}\n", subcommand_help);
274                 let exit_code = if args.is_empty() { 0 } else { 1 };
275                 process::exit(exit_code);
276             }
277         };
278
279         // Some subcommands get extra options
280         match subcommand.as_str() {
281             "test" | "t" => {
282                 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
283                 opts.optmulti(
284                     "",
285                     "test-args",
286                     "extra arguments to be passed for the test tool being used \
287                         (e.g. libtest, compiletest or rustdoc)",
288                     "ARGS",
289                 );
290                 opts.optmulti(
291                     "",
292                     "rustc-args",
293                     "extra options to pass the compiler when running tests",
294                     "ARGS",
295                 );
296                 opts.optflag("", "no-doc", "do not run doc tests");
297                 opts.optflag("", "doc", "only run doc tests");
298                 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
299                 opts.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged");
300                 opts.optopt(
301                     "",
302                     "compare-mode",
303                     "mode describing what file the actual ui output will be compared to",
304                     "COMPARE MODE",
305                 );
306                 opts.optopt(
307                     "",
308                     "pass",
309                     "force {check,build,run}-pass tests to this mode.",
310                     "check | build | run",
311                 );
312                 opts.optopt("", "run", "whether to execute run-* tests", "auto | always | never");
313                 opts.optflag(
314                     "",
315                     "rustfix-coverage",
316                     "enable this to generate a Rustfix coverage file, which is saved in \
317                         `/<build_base>/rustfix_missing_coverage.txt`",
318                 );
319             }
320             "check" | "c" => {
321                 opts.optflag("", "all-targets", "Check all targets");
322             }
323             "bench" => {
324                 opts.optmulti("", "test-args", "extra arguments", "ARGS");
325             }
326             "clippy" => {
327                 opts.optflag("", "fix", "automatically apply lint suggestions");
328             }
329             "doc" | "d" => {
330                 opts.optflag("", "open", "open the docs in a browser");
331             }
332             "clean" => {
333                 opts.optflag("", "all", "clean all build artifacts");
334             }
335             "fmt" => {
336                 opts.optflag("", "check", "check formatting instead of applying.");
337             }
338             _ => {}
339         };
340
341         // fn usage()
342         let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
343             let mut extra_help = String::new();
344
345             // All subcommands except `clean` can have an optional "Available paths" section
346             if verbose {
347                 let config = Config::parse(&["build".to_string()]);
348                 let build = Build::new(config);
349
350                 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
351                 extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
352             } else if !(subcommand.as_str() == "clean" || subcommand.as_str() == "fmt") {
353                 extra_help.push_str(
354                     format!("Run `./x.py {} -h -v` to see a list of available paths.", subcommand)
355                         .as_str(),
356                 );
357             }
358
359             println!("{}", opts.usage(subcommand_help));
360             if !extra_help.is_empty() {
361                 println!("{}", extra_help);
362             }
363             process::exit(exit_code);
364         };
365
366         // Done specifying what options are possible, so do the getopts parsing
367         let matches = opts.parse(args).unwrap_or_else(|e| {
368             // Invalid argument/option format
369             println!("\n{}\n", e);
370             usage(1, &opts, false, &subcommand_help);
371         });
372
373         // Extra sanity check to make sure we didn't hit this crazy corner case:
374         //
375         //     ./x.py --frobulate clean build
376         //            ^-- option  ^     ^- actual subcommand
377         //                        \_ arg to option could be mistaken as subcommand
378         let mut pass_sanity_check = true;
379         match matches.free.get(0) {
380             Some(check_subcommand) => {
381                 if check_subcommand != subcommand {
382                     pass_sanity_check = false;
383                 }
384             }
385             None => {
386                 pass_sanity_check = false;
387             }
388         }
389         if !pass_sanity_check {
390             println!("{}\n", subcommand_help);
391             println!(
392                 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
393                  You may need to move some options to after the subcommand.\n"
394             );
395             process::exit(1);
396         }
397         // Extra help text for some commands
398         match subcommand.as_str() {
399             "build" | "b" => {
400                 subcommand_help.push_str(
401                     "\n
402 Arguments:
403     This subcommand accepts a number of paths to directories to the crates
404     and/or artifacts to compile. For example:
405
406         ./x.py build library/core
407         ./x.py build library/core library/proc_macro
408         ./x.py build library/std --stage 1
409
410     If no arguments are passed then the complete artifacts for that stage are
411     also compiled.
412
413         ./x.py build
414         ./x.py build --stage 1
415
416     For a quick build of a usable compiler, you can pass:
417
418         ./x.py build --stage 1 library/test
419
420     This will first build everything once (like `--stage 0` without further
421     arguments would), and then use the compiler built in stage 0 to build
422     library/test and its dependencies.
423     Once this is done, build/$ARCH/stage1 contains a usable compiler.",
424                 );
425             }
426             "check" | "c" => {
427                 subcommand_help.push_str(
428                     "\n
429 Arguments:
430     This subcommand accepts a number of paths to directories to the crates
431     and/or artifacts to compile. For example:
432
433         ./x.py check library/core
434         ./x.py check library/core library/proc_macro
435
436     If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
437     also that since we use `cargo check`, by default this will automatically enable incremental
438     compilation, so there's no need to pass it separately, though it won't hurt. We also completely
439     ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
440     the compiler.",
441                 );
442             }
443             "clippy" => {
444                 subcommand_help.push_str(
445                     "\n
446 Arguments:
447     This subcommand accepts a number of paths to directories to the crates
448     and/or artifacts to run clippy against. For example:
449
450         ./x.py clippy library/core
451         ./x.py clippy library/core library/proc_macro",
452                 );
453             }
454             "fix" => {
455                 subcommand_help.push_str(
456                     "\n
457 Arguments:
458     This subcommand accepts a number of paths to directories to the crates
459     and/or artifacts to run `cargo fix` against. For example:
460
461         ./x.py fix library/core
462         ./x.py fix library/core library/proc_macro",
463                 );
464             }
465             "fmt" => {
466                 subcommand_help.push_str(
467                     "\n
468 Arguments:
469     This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and
470     fails if it is not. For example:
471
472         ./x.py fmt
473         ./x.py fmt --check",
474                 );
475             }
476             "test" | "t" => {
477                 subcommand_help.push_str(
478                     "\n
479 Arguments:
480     This subcommand accepts a number of paths to test directories that
481     should be compiled and run. For example:
482
483         ./x.py test src/test/ui
484         ./x.py test library/std --test-args hash_map
485         ./x.py test library/std --stage 0 --no-doc
486         ./x.py test src/test/ui --bless
487         ./x.py test src/test/ui --compare-mode nll
488
489     Note that `test src/test/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
490     just like `build library/std --stage N` it tests the compiler produced by the previous
491     stage.
492
493     Execute tool tests with a tool name argument:
494
495         ./x.py test tidy
496
497     If no arguments are passed then the complete artifacts for that stage are
498     compiled and tested.
499
500         ./x.py test
501         ./x.py test --stage 1",
502                 );
503             }
504             "doc" | "d" => {
505                 subcommand_help.push_str(
506                     "\n
507 Arguments:
508     This subcommand accepts a number of paths to directories of documentation
509     to build. For example:
510
511         ./x.py doc src/doc/book
512         ./x.py doc src/doc/nomicon
513         ./x.py doc src/doc/book library/std
514         ./x.py doc library/std --open
515
516     If no arguments are passed then everything is documented:
517
518         ./x.py doc
519         ./x.py doc --stage 1",
520                 );
521             }
522             "run" | "r" => {
523                 subcommand_help.push_str(
524                     "\n
525 Arguments:
526     This subcommand accepts a number of paths to tools to build and run. For
527     example:
528
529         ./x.py run src/tools/expand-yaml-anchors
530
531     At least a tool needs to be called.",
532                 );
533             }
534             "setup" => {
535                 subcommand_help.push_str(&format!(
536                     "\n
537 x.py setup creates a `config.toml` which changes the defaults for x.py itself.
538
539 Arguments:
540     This subcommand accepts a 'profile' to use for builds. For example:
541
542         ./x.py setup library
543
544     The profile is optional and you will be prompted interactively if it is not given.
545     The following profiles are available:
546
547 {}",
548                     Profile::all_for_help("        ").trim_end()
549                 ));
550             }
551             _ => {}
552         };
553         // Get any optional paths which occur after the subcommand
554         let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
555
556         let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
557         let verbose = matches.opt_present("verbose");
558
559         // User passed in -h/--help?
560         if matches.opt_present("help") {
561             usage(0, &opts, verbose, &subcommand_help);
562         }
563
564         let cmd = match subcommand.as_str() {
565             "build" | "b" => Subcommand::Build { paths },
566             "check" | "c" => {
567                 if matches.opt_present("all-targets") {
568                     eprintln!(
569                         "Warning: --all-targets is now on by default and does not need to be passed explicitly."
570                     );
571                 }
572                 Subcommand::Check { paths }
573             }
574             "clippy" => Subcommand::Clippy { paths, fix: matches.opt_present("fix") },
575             "fix" => Subcommand::Fix { paths },
576             "test" | "t" => Subcommand::Test {
577                 paths,
578                 bless: matches.opt_present("bless"),
579                 force_rerun: matches.opt_present("force-rerun"),
580                 compare_mode: matches.opt_str("compare-mode"),
581                 pass: matches.opt_str("pass"),
582                 run: matches.opt_str("run"),
583                 test_args: matches.opt_strs("test-args"),
584                 rustc_args: matches.opt_strs("rustc-args"),
585                 fail_fast: !matches.opt_present("no-fail-fast"),
586                 rustfix_coverage: matches.opt_present("rustfix-coverage"),
587                 doc_tests: if matches.opt_present("doc") {
588                     DocTests::Only
589                 } else if matches.opt_present("no-doc") {
590                     DocTests::No
591                 } else {
592                     DocTests::Yes
593                 },
594             },
595             "bench" => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") },
596             "doc" | "d" => Subcommand::Doc { paths, open: matches.opt_present("open") },
597             "clean" => {
598                 if !paths.is_empty() {
599                     println!("\nclean does not take a path argument\n");
600                     usage(1, &opts, verbose, &subcommand_help);
601                 }
602
603                 Subcommand::Clean { all: matches.opt_present("all") }
604             }
605             "fmt" => Subcommand::Format { check: matches.opt_present("check"), paths },
606             "dist" => Subcommand::Dist { paths },
607             "install" => Subcommand::Install { paths },
608             "run" | "r" => {
609                 if paths.is_empty() {
610                     println!("\nrun requires at least a path!\n");
611                     usage(1, &opts, verbose, &subcommand_help);
612                 }
613                 Subcommand::Run { paths }
614             }
615             "setup" => {
616                 let profile = if paths.len() > 1 {
617                     println!("\nat most one profile can be passed to setup\n");
618                     usage(1, &opts, verbose, &subcommand_help)
619                 } else if let Some(path) = paths.pop() {
620                     let profile_string = t!(path.into_os_string().into_string().map_err(
621                         |path| format!("{} is not a valid UTF8 string", path.to_string_lossy())
622                     ));
623
624                     profile_string.parse().unwrap_or_else(|err| {
625                         eprintln!("error: {}", err);
626                         eprintln!("help: the available profiles are:");
627                         eprint!("{}", Profile::all_for_help("- "));
628                         std::process::exit(1);
629                     })
630                 } else {
631                     t!(crate::setup::interactive_path())
632                 };
633                 Subcommand::Setup { profile }
634             }
635             _ => {
636                 usage(1, &opts, verbose, &subcommand_help);
637             }
638         };
639
640         if let Subcommand::Check { .. } = &cmd {
641             if matches.opt_str("keep-stage").is_some()
642                 || matches.opt_str("keep-stage-std").is_some()
643             {
644                 println!("--keep-stage not yet supported for x.py check");
645                 process::exit(1);
646             }
647         }
648
649         Flags {
650             verbose: matches.opt_count("verbose"),
651             stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")),
652             dry_run: matches.opt_present("dry-run"),
653             on_fail: matches.opt_str("on-fail"),
654             rustc_error_format: matches.opt_str("error-format"),
655             json_output: matches.opt_present("json-output"),
656             keep_stage: matches
657                 .opt_strs("keep-stage")
658                 .into_iter()
659                 .map(|j| j.parse().expect("`keep-stage` should be a number"))
660                 .collect(),
661             keep_stage_std: matches
662                 .opt_strs("keep-stage-std")
663                 .into_iter()
664                 .map(|j| j.parse().expect("`keep-stage-std` should be a number"))
665                 .collect(),
666             host: if matches.opt_present("host") {
667                 Some(
668                     split(&matches.opt_strs("host"))
669                         .into_iter()
670                         .map(|x| TargetSelection::from_user(&x))
671                         .collect::<Vec<_>>(),
672                 )
673             } else {
674                 None
675             },
676             target: if matches.opt_present("target") {
677                 Some(
678                     split(&matches.opt_strs("target"))
679                         .into_iter()
680                         .map(|x| TargetSelection::from_user(&x))
681                         .collect::<Vec<_>>(),
682                 )
683             } else {
684                 None
685             },
686             config: cfg_file,
687             jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")),
688             cmd,
689             incremental: matches.opt_present("incremental"),
690             exclude: split(&matches.opt_strs("exclude"))
691                 .into_iter()
692                 .map(|p| p.into())
693                 .collect::<Vec<_>>(),
694             include_default_paths: matches.opt_present("include-default-paths"),
695             deny_warnings: parse_deny_warnings(&matches),
696             llvm_skip_rebuild: matches.opt_str("llvm-skip-rebuild").map(|s| s.to_lowercase()).map(
697                 |s| s.parse::<bool>().expect("`llvm-skip-rebuild` should be either true or false"),
698             ),
699             color: matches
700                 .opt_get_default("color", Color::Auto)
701                 .expect("`color` should be `always`, `never`, or `auto`"),
702             rust_profile_use: matches.opt_str("rust-profile-use"),
703             rust_profile_generate: matches.opt_str("rust-profile-generate"),
704             llvm_profile_use: matches.opt_str("llvm-profile-use"),
705             llvm_profile_generate: matches.opt_present("llvm-profile-generate"),
706         }
707     }
708 }
709
710 impl Subcommand {
711     pub fn test_args(&self) -> Vec<&str> {
712         match *self {
713             Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
714                 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
715             }
716             _ => Vec::new(),
717         }
718     }
719
720     pub fn rustc_args(&self) -> Vec<&str> {
721         match *self {
722             Subcommand::Test { ref rustc_args, .. } => {
723                 rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
724             }
725             _ => Vec::new(),
726         }
727     }
728
729     pub fn fail_fast(&self) -> bool {
730         match *self {
731             Subcommand::Test { fail_fast, .. } => fail_fast,
732             _ => false,
733         }
734     }
735
736     pub fn doc_tests(&self) -> DocTests {
737         match *self {
738             Subcommand::Test { doc_tests, .. } => doc_tests,
739             _ => DocTests::Yes,
740         }
741     }
742
743     pub fn bless(&self) -> bool {
744         match *self {
745             Subcommand::Test { bless, .. } => bless,
746             _ => false,
747         }
748     }
749
750     pub fn force_rerun(&self) -> bool {
751         match *self {
752             Subcommand::Test { force_rerun, .. } => force_rerun,
753             _ => false,
754         }
755     }
756
757     pub fn rustfix_coverage(&self) -> bool {
758         match *self {
759             Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
760             _ => false,
761         }
762     }
763
764     pub fn compare_mode(&self) -> Option<&str> {
765         match *self {
766             Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
767             _ => None,
768         }
769     }
770
771     pub fn pass(&self) -> Option<&str> {
772         match *self {
773             Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
774             _ => None,
775         }
776     }
777
778     pub fn run(&self) -> Option<&str> {
779         match *self {
780             Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
781             _ => None,
782         }
783     }
784
785     pub fn open(&self) -> bool {
786         match *self {
787             Subcommand::Doc { open, .. } => open,
788             _ => false,
789         }
790     }
791 }
792
793 fn split(s: &[String]) -> Vec<String> {
794     s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect()
795 }
796
797 fn parse_deny_warnings(matches: &getopts::Matches) -> Option<bool> {
798     match matches.opt_str("warnings").as_deref() {
799         Some("deny") => Some(true),
800         Some("warn") => Some(false),
801         Some(value) => {
802             eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,);
803             process::exit(1);
804         }
805         None => None,
806     }
807 }