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