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