]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/flags.rs
Various minor/cosmetic improvements to code
[rust.git] / src / bootstrap / flags.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Command-line interface of the rustbuild build system.
12 //!
13 //! This module implements the command-line parsing of the build system which
14 //! has various flags to configure how it's run.
15
16 use std::fs;
17 use std::path::PathBuf;
18 use std::process;
19
20 use getopts::Options;
21
22 use builder::Builder;
23 use config::Config;
24 use metadata;
25 use {Build, DocTests};
26
27 use cache::{Interned, INTERNER};
28
29 /// Deserialized version of all flags for this compile.
30 pub struct Flags {
31     pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo
32     pub on_fail: Option<String>,
33     pub stage: Option<u32>,
34     pub keep_stage: Vec<u32>,
35
36     pub host: Vec<Interned<String>>,
37     pub target: Vec<Interned<String>>,
38     pub config: Option<PathBuf>,
39     pub jobs: Option<u32>,
40     pub cmd: Subcommand,
41     pub incremental: bool,
42     pub exclude: Vec<PathBuf>,
43     pub rustc_error_format: Option<String>,
44     pub dry_run: bool,
45
46     // true => deny
47     pub warnings: Option<bool>,
48 }
49
50 pub enum Subcommand {
51     Build {
52         paths: Vec<PathBuf>,
53     },
54     Check {
55         paths: Vec<PathBuf>,
56     },
57     Doc {
58         paths: Vec<PathBuf>,
59     },
60     Test {
61         paths: Vec<PathBuf>,
62         /// Whether to automatically update stderr/stdout files
63         bless: bool,
64         compare_mode: Option<String>,
65         test_args: Vec<String>,
66         rustc_args: Vec<String>,
67         fail_fast: bool,
68         doc_tests: DocTests,
69     },
70     Bench {
71         paths: Vec<PathBuf>,
72         test_args: Vec<String>,
73     },
74     Clean {
75         all: bool,
76     },
77     Dist {
78         paths: Vec<PathBuf>,
79     },
80     Install {
81         paths: Vec<PathBuf>,
82     },
83 }
84
85 impl Default for Subcommand {
86     fn default() -> Subcommand {
87         Subcommand::Build {
88             paths: vec![PathBuf::from("nowhere")],
89         }
90     }
91 }
92
93 impl Flags {
94     pub fn parse(args: &[String]) -> Flags {
95         let mut extra_help = String::new();
96         let mut subcommand_help = String::from("\
97 Usage: x.py <subcommand> [options] [<paths>...]
98
99 Subcommands:
100     build       Compile either the compiler or libraries
101     check       Compile either the compiler or libraries, using cargo check
102     test        Build and run some test suites
103     bench       Build and run some benchmarks
104     doc         Build documentation
105     clean       Clean out build directories
106     dist        Build distribution artifacts
107     install     Install distribution artifacts
108
109 To learn more about a subcommand, run `./x.py <subcommand> -h`"
110         );
111
112         let mut opts = Options::new();
113         // Options common to all subcommands
114         opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
115         opts.optflag("i", "incremental", "use incremental compilation");
116         opts.optopt("", "config", "TOML configuration file for build", "FILE");
117         opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
118         opts.optmulti("", "host", "host targets to build", "HOST");
119         opts.optmulti("", "target", "target targets to build", "TARGET");
120         opts.optmulti("", "exclude", "build paths to exclude", "PATH");
121         opts.optopt("", "on-fail", "command to run on failure", "CMD");
122         opts.optflag("", "dry-run", "dry run; don't build anything");
123         opts.optopt("", "stage",
124             "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \
125              bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)",
126             "N");
127         opts.optmulti("", "keep-stage", "stage(s) to keep without recompiling \
128             (pass multiple times to keep e.g., both stages 0 and 1)", "N");
129         opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
130         opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS");
131         opts.optflag("h", "help", "print this help message");
132         opts.optopt(
133             "",
134             "warnings",
135             "if value is deny, will deny warnings, otherwise use default",
136             "VALUE",
137         );
138         opts.optopt("", "error-format", "rustc error format", "FORMAT");
139
140         // fn usage()
141         let usage =
142             |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
143                 println!("{}", opts.usage(subcommand_help));
144                 if !extra_help.is_empty() {
145                     println!("{}", extra_help);
146                 }
147                 process::exit(exit_code);
148             };
149
150         // We can't use getopt to parse the options until we have completed specifying which
151         // options are valid, but under the current implementation, some options are conditional on
152         // the subcommand. Therefore we must manually identify the subcommand first, so that we can
153         // complete the definition of the options.  Then we can use the getopt::Matches object from
154         // there on out.
155         let subcommand = args.iter().find(|&s| {
156             (s == "build")
157                 || (s == "check")
158                 || (s == "test")
159                 || (s == "bench")
160                 || (s == "doc")
161                 || (s == "clean")
162                 || (s == "dist")
163                 || (s == "install")
164         });
165         let subcommand = match subcommand {
166             Some(s) => s,
167             None => {
168                 // No or an invalid subcommand -- show the general usage and subcommand help
169                 // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid
170                 // subcommand.
171                 println!("{}\n", subcommand_help);
172                 let exit_code = if args.is_empty() { 0 } else { 1 };
173                 process::exit(exit_code);
174             }
175         };
176
177         // Some subcommands get extra options
178         match subcommand.as_str() {
179             "test" => {
180                 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
181                 opts.optmulti("", "test-args", "extra arguments", "ARGS");
182                 opts.optmulti(
183                     "",
184                     "rustc-args",
185                     "extra options to pass the compiler when running tests",
186                     "ARGS",
187                 );
188                 opts.optflag("", "no-doc", "do not run doc tests");
189                 opts.optflag("", "doc", "only run doc tests");
190                 opts.optflag(
191                     "",
192                     "bless",
193                     "update all stderr/stdout files of failing ui tests",
194                 );
195                 opts.optopt(
196                     "",
197                     "compare-mode",
198                     "mode describing what file the actual ui output will be compared to",
199                     "COMPARE MODE",
200                 );
201             }
202             "bench" => {
203                 opts.optmulti("", "test-args", "extra arguments", "ARGS");
204             }
205             "clean" => {
206                 opts.optflag("", "all", "clean all build artifacts");
207             }
208             _ => {}
209         };
210
211         // Done specifying what options are possible, so do the getopts parsing
212         let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
213             // Invalid argument/option format
214             println!("\n{}\n", e);
215             usage(1, &opts, &subcommand_help, &extra_help);
216         });
217         // Extra sanity check to make sure we didn't hit this crazy corner case:
218         //
219         //     ./x.py --frobulate clean build
220         //            ^-- option  ^     ^- actual subcommand
221         //                        \_ arg to option could be mistaken as subcommand
222         let mut pass_sanity_check = true;
223         match matches.free.get(0) {
224             Some(check_subcommand) => {
225                 if check_subcommand != subcommand {
226                     pass_sanity_check = false;
227                 }
228             }
229             None => {
230                 pass_sanity_check = false;
231             }
232         }
233         if !pass_sanity_check {
234             println!("{}\n", subcommand_help);
235             println!(
236                 "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
237                  You may need to move some options to after the subcommand.\n"
238             );
239             process::exit(1);
240         }
241         // Extra help text for some commands
242         match subcommand.as_str() {
243             "build" => {
244                 subcommand_help.push_str(
245                     "\n
246 Arguments:
247     This subcommand accepts a number of paths to directories to the crates
248     and/or artifacts to compile. For example:
249
250         ./x.py build src/libcore
251         ./x.py build src/libcore src/libproc_macro
252         ./x.py build src/libstd --stage 1
253
254     If no arguments are passed then the complete artifacts for that stage are
255     also compiled.
256
257         ./x.py build
258         ./x.py build --stage 1
259
260     For a quick build of a usable compiler, you can pass:
261
262         ./x.py build --stage 1 src/libtest
263
264     This will first build everything once (like `--stage 0` without further
265     arguments would), and then use the compiler built in stage 0 to build
266     src/libtest and its dependencies.
267     Once this is done, build/$ARCH/stage1 contains a usable compiler.",
268                 );
269             }
270             "check" => {
271                 subcommand_help.push_str(
272                     "\n
273 Arguments:
274     This subcommand accepts a number of paths to directories to the crates
275     and/or artifacts to compile. For example:
276
277         ./x.py check src/libcore
278         ./x.py check src/libcore src/libproc_macro
279
280     If no arguments are passed then the complete artifacts are compiled: std, test, and rustc. Note
281     also that since we use `cargo check`, by default this will automatically enable incremental
282     compilation, so there's no need to pass it separately, though it won't hurt. We also completely
283     ignore the stage passed, as there's no way to compile in non-stage 0 without actually building
284     the compiler.",
285                 );
286             }
287             "test" => {
288                 subcommand_help.push_str(
289                     "\n
290 Arguments:
291     This subcommand accepts a number of paths to directories to tests that
292     should be compiled and run. For example:
293
294         ./x.py test src/test/run-pass
295         ./x.py test src/libstd --test-args hash_map
296         ./x.py test src/libstd --stage 0 --no-doc
297         ./x.py test src/test/ui --bless
298         ./x.py test src/test/ui --compare-mode nll
299
300     Note that `test src/test/* --stage N` does NOT depend on `build src/rustc --stage N`;
301     just like `build src/libstd --stage N` it tests the compiler produced by the previous
302     stage.
303
304     If no arguments are passed then the complete artifacts for that stage are
305     compiled and tested.
306
307         ./x.py test
308         ./x.py test --stage 1",
309                 );
310             }
311             "doc" => {
312                 subcommand_help.push_str(
313                     "\n
314 Arguments:
315     This subcommand accepts a number of paths to directories of documentation
316     to build. For example:
317
318         ./x.py doc src/doc/book
319         ./x.py doc src/doc/nomicon
320         ./x.py doc src/doc/book src/libstd
321
322     If no arguments are passed then everything is documented:
323
324         ./x.py doc
325         ./x.py doc --stage 1",
326                 );
327             }
328             _ => {}
329         };
330         // Get any optional paths which occur after the subcommand
331         let paths = matches.free[1..]
332             .iter()
333             .map(|p| p.into())
334             .collect::<Vec<PathBuf>>();
335
336         let cfg_file = matches.opt_str("config").map(PathBuf::from).or_else(|| {
337             if fs::metadata("config.toml").is_ok() {
338                 Some(PathBuf::from("config.toml"))
339             } else {
340                 None
341             }
342         });
343
344         // All subcommands except `clean` can have an optional "Available paths" section
345         if matches.opt_present("verbose") {
346             let config = Config::parse(&["build".to_string()]);
347             let mut build = Build::new(config);
348             metadata::build(&mut build);
349
350             let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
351             extra_help.push_str(maybe_rules_help.unwrap_or_default().as_str());
352         } else if subcommand.as_str() != "clean" {
353             extra_help.push_str(
354                 format!(
355                     "Run `./x.py {} -h -v` to see a list of available paths.",
356                     subcommand
357                 ).as_str(),
358             );
359         }
360
361         // User passed in -h/--help?
362         if matches.opt_present("help") {
363             usage(0, &opts, &subcommand_help, &extra_help);
364         }
365
366         let cmd = match subcommand.as_str() {
367             "build" => Subcommand::Build { paths },
368             "check" => Subcommand::Check { paths },
369             "test" => Subcommand::Test {
370                 paths,
371                 bless: matches.opt_present("bless"),
372                 compare_mode: matches.opt_str("compare-mode"),
373                 test_args: matches.opt_strs("test-args"),
374                 rustc_args: matches.opt_strs("rustc-args"),
375                 fail_fast: !matches.opt_present("no-fail-fast"),
376                 doc_tests: if matches.opt_present("doc") {
377                     DocTests::Only
378                 } else if matches.opt_present("no-doc") {
379                     DocTests::No
380                 } else {
381                     DocTests::Yes
382                 },
383             },
384             "bench" => Subcommand::Bench {
385                 paths,
386                 test_args: matches.opt_strs("test-args"),
387             },
388             "doc" => Subcommand::Doc { paths },
389             "clean" => {
390                 if !paths.is_empty() {
391                     println!("\nclean does not take a path argument\n");
392                     usage(1, &opts, &subcommand_help, &extra_help);
393                 }
394
395                 Subcommand::Clean {
396                     all: matches.opt_present("all"),
397                 }
398             }
399             "dist" => Subcommand::Dist { paths },
400             "install" => Subcommand::Install { paths },
401             _ => {
402                 usage(1, &opts, &subcommand_help, &extra_help);
403             }
404         };
405
406         Flags {
407             verbose: matches.opt_count("verbose"),
408             stage: matches.opt_str("stage").map(|j| j.parse().unwrap()),
409             dry_run: matches.opt_present("dry-run"),
410             on_fail: matches.opt_str("on-fail"),
411             rustc_error_format: matches.opt_str("error-format"),
412             keep_stage: matches.opt_strs("keep-stage")
413                 .into_iter().map(|j| j.parse().unwrap())
414                 .collect(),
415             host: split(&matches.opt_strs("host"))
416                 .into_iter()
417                 .map(|x| INTERNER.intern_string(x))
418                 .collect::<Vec<_>>(),
419             target: split(&matches.opt_strs("target"))
420                 .into_iter()
421                 .map(|x| INTERNER.intern_string(x))
422                 .collect::<Vec<_>>(),
423             config: cfg_file,
424             jobs: matches.opt_str("jobs").map(|j| j.parse().unwrap()),
425             cmd,
426             incremental: matches.opt_present("incremental"),
427             exclude: split(&matches.opt_strs("exclude"))
428                 .into_iter()
429                 .map(|p| p.into())
430                 .collect::<Vec<_>>(),
431             warnings: matches.opt_str("warnings").map(|v| v == "deny"),
432         }
433     }
434 }
435
436 impl Subcommand {
437     pub fn test_args(&self) -> Vec<&str> {
438         match *self {
439             Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
440                 test_args
441                     .iter()
442                     .flat_map(|s| s.split_whitespace())
443                     .collect()
444             }
445             _ => Vec::new(),
446         }
447     }
448
449     pub fn rustc_args(&self) -> Vec<&str> {
450         match *self {
451             Subcommand::Test { ref rustc_args, .. } => rustc_args
452                 .iter()
453                 .flat_map(|s| s.split_whitespace())
454                 .collect(),
455             _ => Vec::new(),
456         }
457     }
458
459     pub fn fail_fast(&self) -> bool {
460         match *self {
461             Subcommand::Test { fail_fast, .. } => fail_fast,
462             _ => false,
463         }
464     }
465
466     pub fn doc_tests(&self) -> DocTests {
467         match *self {
468             Subcommand::Test { doc_tests, .. } => doc_tests,
469             _ => DocTests::Yes,
470         }
471     }
472
473     pub fn bless(&self) -> bool {
474         match *self {
475             Subcommand::Test { bless, .. } => bless,
476             _ => false,
477         }
478     }
479
480     pub fn compare_mode(&self) -> Option<&str> {
481         match *self {
482             Subcommand::Test {
483                 ref compare_mode, ..
484             } => compare_mode.as_ref().map(|s| &s[..]),
485             _ => None,
486         }
487     }
488 }
489
490 fn split(s: &[String]) -> Vec<String> {
491     s.iter()
492         .flat_map(|s| s.split(','))
493         .map(|s| s.to_string())
494         .collect()
495 }