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