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