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