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