]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/flags.rs
5804df34e8b383f21798c20fcf0791244d9628ac
[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::env;
17 use std::fs;
18 use std::path::PathBuf;
19 use std::process;
20
21 use getopts::Options;
22
23 use Build;
24 use config::Config;
25 use metadata;
26 use step;
27
28 /// Deserialized version of all flags for this compile.
29 pub struct Flags {
30     pub verbose: usize, // verbosity level: 0 == not verbose, 1 == verbose, 2 == very verbose
31     pub on_fail: Option<String>,
32     pub stage: Option<u32>,
33     pub keep_stage: Option<u32>,
34     pub build: String,
35     pub host: Vec<String>,
36     pub target: Vec<String>,
37     pub config: Option<PathBuf>,
38     pub src: PathBuf,
39     pub jobs: Option<u32>,
40     pub cmd: Subcommand,
41     pub incremental: bool,
42 }
43
44 pub enum Subcommand {
45     Build {
46         paths: Vec<PathBuf>,
47     },
48     Doc {
49         paths: Vec<PathBuf>,
50     },
51     Test {
52         paths: Vec<PathBuf>,
53         test_args: Vec<String>,
54         fail_fast: bool,
55     },
56     Bench {
57         paths: Vec<PathBuf>,
58         test_args: Vec<String>,
59     },
60     Clean,
61     Dist {
62         paths: Vec<PathBuf>,
63     },
64     Install {
65         paths: Vec<PathBuf>,
66     },
67 }
68
69 impl Flags {
70     pub fn parse(args: &[String]) -> Flags {
71         let mut extra_help = String::new();
72         let mut subcommand_help = format!("\
73 Usage: x.py <subcommand> [options] [<paths>...]
74
75 Subcommands:
76     build       Compile either the compiler or libraries
77     test        Build and run some test suites
78     bench       Build and run some benchmarks
79     doc         Build documentation
80     clean       Clean out build directories
81     dist        Build distribution artifacts
82     install     Install distribution artifacts
83
84 To learn more about a subcommand, run `./x.py <subcommand> -h`");
85
86         let mut opts = Options::new();
87         // Options common to all subcommands
88         opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
89         opts.optflag("i", "incremental", "use incremental compilation");
90         opts.optopt("", "config", "TOML configuration file for build", "FILE");
91         opts.optopt("", "build", "build target of the stage0 compiler", "BUILD");
92         opts.optmulti("", "host", "host targets to build", "HOST");
93         opts.optmulti("", "target", "target targets to build", "TARGET");
94         opts.optopt("", "on-fail", "command to run on failure", "CMD");
95         opts.optopt("", "stage", "stage to build", "N");
96         opts.optopt("", "keep-stage", "stage to keep without recompiling", "N");
97         opts.optopt("", "src", "path to the root of the rust checkout", "DIR");
98         opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS");
99         opts.optflag("h", "help", "print this help message");
100
101         // fn usage()
102         let usage = |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
103             println!("{}", opts.usage(subcommand_help));
104             if !extra_help.is_empty() {
105                 println!("{}", extra_help);
106             }
107             process::exit(exit_code);
108         };
109
110         // We can't use getopt to parse the options until we have completed specifying which
111         // options are valid, but under the current implementation, some options are conditional on
112         // the subcommand. Therefore we must manually identify the subcommand first, so that we can
113         // complete the definition of the options.  Then we can use the getopt::Matches object from
114         // there on out.
115         let subcommand = args.iter().find(|&s|
116             (s == "build")
117             || (s == "test")
118             || (s == "bench")
119             || (s == "doc")
120             || (s == "clean")
121             || (s == "dist")
122             || (s == "install"));
123         let subcommand = match subcommand {
124             Some(s) => s,
125             None => {
126                 // No subcommand -- show the general usage and subcommand help
127                 println!("{}\n", subcommand_help);
128                 process::exit(0);
129             }
130         };
131
132         // Some subcommands get extra options
133         match subcommand.as_str() {
134             "test"  => {
135                 opts.optflag("", "no-fail-fast", "Run all tests regardless of failure");
136                 opts.optmulti("", "test-args", "extra arguments", "ARGS");
137             },
138             "bench" => { opts.optmulti("", "test-args", "extra arguments", "ARGS"); },
139             _ => { },
140         };
141
142         // Done specifying what options are possible, so do the getopts parsing
143         let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
144             // Invalid argument/option format
145             println!("\n{}\n", e);
146             usage(1, &opts, &subcommand_help, &extra_help);
147         });
148         // Extra sanity check to make sure we didn't hit this crazy corner case:
149         //
150         //     ./x.py --frobulate clean build
151         //            ^-- option  ^     ^- actual subcommand
152         //                        \_ arg to option could be mistaken as subcommand
153         let mut pass_sanity_check = true;
154         match matches.free.get(0) {
155             Some(check_subcommand) => {
156                 if check_subcommand != subcommand {
157                     pass_sanity_check = false;
158                 }
159             },
160             None => {
161                 pass_sanity_check = false;
162             }
163         }
164         if !pass_sanity_check {
165             println!("{}\n", subcommand_help);
166             println!("Sorry, I couldn't figure out which subcommand you were trying to specify.\n\
167                       You may need to move some options to after the subcommand.\n");
168             process::exit(1);
169         }
170         // Extra help text for some commands
171         match subcommand.as_str() {
172             "build" => {
173                 subcommand_help.push_str("\n
174 Arguments:
175     This subcommand accepts a number of paths to directories to the crates
176     and/or artifacts to compile. For example:
177
178         ./x.py build src/libcore
179         ./x.py build src/libcore src/libproc_macro
180         ./x.py build src/libstd --stage 1
181
182     If no arguments are passed then the complete artifacts for that stage are
183     also compiled.
184
185         ./x.py build
186         ./x.py build --stage 1
187
188     For a quick build of a usable compiler, you can pass:
189
190         ./x.py build --stage 1 src/libtest
191
192     This will first build everything once (like --stage 0 without further
193     arguments would), and then use the compiler built in stage 0 to build
194     src/libtest and its dependencies.
195     Once this is done, build/$ARCH/stage1 contains a usable compiler.");
196             }
197             "test" => {
198                 subcommand_help.push_str("\n
199 Arguments:
200     This subcommand accepts a number of paths to directories to tests that
201     should be compiled and run. For example:
202
203         ./x.py test src/test/run-pass
204         ./x.py test src/libstd --test-args hash_map
205         ./x.py test src/libstd --stage 0
206
207     If no arguments are passed then the complete artifacts for that stage are
208     compiled and tested.
209
210         ./x.py test
211         ./x.py test --stage 1");
212             }
213             "doc" => {
214                 subcommand_help.push_str("\n
215 Arguments:
216     This subcommand accepts a number of paths to directories of documentation
217     to build. For example:
218
219         ./x.py doc src/doc/book
220         ./x.py doc src/doc/nomicon
221         ./x.py doc src/doc/book src/libstd
222
223     If no arguments are passed then everything is documented:
224
225         ./x.py doc
226         ./x.py doc --stage 1");
227             }
228             _ => { }
229         };
230         // Get any optional paths which occur after the subcommand
231         let cwd = t!(env::current_dir());
232         let paths = matches.free[1..].iter().map(|p| cwd.join(p)).collect::<Vec<_>>();
233
234         let cfg_file = matches.opt_str("config").map(PathBuf::from).or_else(|| {
235             if fs::metadata("config.toml").is_ok() {
236                 Some(PathBuf::from("config.toml"))
237             } else {
238                 None
239             }
240         });
241
242         // All subcommands can have an optional "Available paths" section
243         if matches.opt_present("verbose") {
244             let flags = Flags::parse(&["build".to_string()]);
245             let mut config = Config::parse(&flags.build, cfg_file.clone());
246             config.build = flags.build.clone();
247             let mut build = Build::new(flags, config);
248             metadata::build(&mut build);
249             let maybe_rules_help = step::build_rules(&build).get_help(subcommand);
250             if maybe_rules_help.is_some() {
251                 extra_help.push_str(maybe_rules_help.unwrap().as_str());
252             }
253         } else {
254             extra_help.push_str(format!("Run `./x.py {} -h -v` to see a list of available paths.",
255                      subcommand).as_str());
256         }
257
258         // User passed in -h/--help?
259         if matches.opt_present("help") {
260             usage(0, &opts, &subcommand_help, &extra_help);
261         }
262
263         let cmd = match subcommand.as_str() {
264             "build" => {
265                 Subcommand::Build { paths: paths }
266             }
267             "test" => {
268                 Subcommand::Test {
269                     paths: paths,
270                     test_args: matches.opt_strs("test-args"),
271                     fail_fast: !matches.opt_present("no-fail-fast"),
272                 }
273             }
274             "bench" => {
275                 Subcommand::Bench {
276                     paths: paths,
277                     test_args: matches.opt_strs("test-args"),
278                 }
279             }
280             "doc" => {
281                 Subcommand::Doc { paths: paths }
282             }
283             "clean" => {
284                 if paths.len() > 0 {
285                     println!("\nclean takes no arguments\n");
286                     usage(1, &opts, &subcommand_help, &extra_help);
287                 }
288                 Subcommand::Clean
289             }
290             "dist" => {
291                 Subcommand::Dist {
292                     paths: paths,
293                 }
294             }
295             "install" => {
296                 Subcommand::Install {
297                     paths: paths,
298                 }
299             }
300             _ => {
301                 usage(1, &opts, &subcommand_help, &extra_help);
302             }
303         };
304
305
306         let mut stage = matches.opt_str("stage").map(|j| j.parse().unwrap());
307
308         if matches.opt_present("incremental") && stage.is_none() {
309             stage = Some(1);
310         }
311
312         let cwd = t!(env::current_dir());
313         let src = matches.opt_str("src").map(PathBuf::from)
314             .or_else(|| env::var_os("SRC").map(PathBuf::from))
315             .unwrap_or(cwd);
316
317         Flags {
318             verbose: matches.opt_count("verbose"),
319             stage: stage,
320             on_fail: matches.opt_str("on-fail"),
321             keep_stage: matches.opt_str("keep-stage").map(|j| j.parse().unwrap()),
322             build: matches.opt_str("build").unwrap_or_else(|| {
323                 env::var("BUILD").unwrap()
324             }),
325             host: split(matches.opt_strs("host")),
326             target: split(matches.opt_strs("target")),
327             config: cfg_file,
328             src: src,
329             jobs: matches.opt_str("jobs").map(|j| j.parse().unwrap()),
330             cmd: cmd,
331             incremental: matches.opt_present("incremental"),
332         }
333     }
334 }
335
336 impl Subcommand {
337     pub fn test_args(&self) -> Vec<&str> {
338         match *self {
339             Subcommand::Test { ref test_args, .. } |
340             Subcommand::Bench { ref test_args, .. } => {
341                 test_args.iter().flat_map(|s| s.split_whitespace()).collect()
342             }
343             _ => Vec::new(),
344         }
345     }
346
347     pub fn fail_fast(&self) -> bool {
348         match *self {
349             Subcommand::Test { fail_fast, .. } => fail_fast,
350             _ => false,
351         }
352     }
353 }
354
355 fn split(s: Vec<String>) -> Vec<String> {
356     s.iter().flat_map(|s| s.split(',')).map(|s| s.to_string()).collect()
357 }