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