]>
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 | ||
7453a54e SL |
16 | use std::fs; |
17 | use std::path::PathBuf; | |
18 | use std::process; | |
7453a54e | 19 | |
cc61c64b | 20 | use getopts::Options; |
c30ab7b3 | 21 | |
94b46f34 | 22 | use builder::Builder; |
c30ab7b3 SL |
23 | use config::Config; |
24 | use metadata; | |
94b46f34 | 25 | use {Build, DocTests}; |
3b2f2976 XL |
26 | |
27 | use cache::{Interned, INTERNER}; | |
7453a54e | 28 | |
a7813a04 | 29 | /// Deserialized version of all flags for this compile. |
7453a54e | 30 | pub struct Flags { |
0531ce1d | 31 | pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo |
8bb4bdeb | 32 | pub on_fail: Option<String>, |
7453a54e | 33 | pub stage: Option<u32>, |
476ff2be | 34 | pub keep_stage: Option<u32>, |
3b2f2976 XL |
35 | |
36 | pub host: Vec<Interned<String>>, | |
37 | pub target: Vec<Interned<String>>, | |
7453a54e | 38 | pub config: Option<PathBuf>, |
7453a54e | 39 | pub jobs: Option<u32>, |
c30ab7b3 | 40 | pub cmd: Subcommand, |
32a655c1 | 41 | pub incremental: bool, |
2c00a5a8 | 42 | pub exclude: Vec<PathBuf>, |
0531ce1d | 43 | pub rustc_error_format: Option<String>, |
83c7162d XL |
44 | pub dry_run: bool, |
45 | ||
46 | // true => deny | |
47 | pub warnings: Option<bool>, | |
32a655c1 SL |
48 | } |
49 | ||
c30ab7b3 SL |
50 | pub enum Subcommand { |
51 | Build { | |
52 | paths: Vec<PathBuf>, | |
53 | }, | |
2c00a5a8 XL |
54 | Check { |
55 | paths: Vec<PathBuf>, | |
56 | }, | |
c30ab7b3 SL |
57 | Doc { |
58 | paths: Vec<PathBuf>, | |
59 | }, | |
60 | Test { | |
61 | paths: Vec<PathBuf>, | |
94b46f34 XL |
62 | /// Whether to automatically update stderr/stdout files |
63 | bless: bool, | |
64 | compare_mode: Option<String>, | |
c30ab7b3 | 65 | test_args: Vec<String>, |
2c00a5a8 | 66 | rustc_args: Vec<String>, |
041b39d2 | 67 | fail_fast: bool, |
83c7162d | 68 | doc_tests: DocTests, |
c30ab7b3 | 69 | }, |
476ff2be SL |
70 | Bench { |
71 | paths: Vec<PathBuf>, | |
72 | test_args: Vec<String>, | |
73 | }, | |
ea8adc8c XL |
74 | Clean { |
75 | all: bool, | |
76 | }, | |
c30ab7b3 | 77 | Dist { |
32a655c1 | 78 | paths: Vec<PathBuf>, |
7cac9316 XL |
79 | }, |
80 | Install { | |
81 | paths: Vec<PathBuf>, | |
c30ab7b3 | 82 | }, |
7453a54e SL |
83 | } |
84 | ||
3b2f2976 XL |
85 | impl Default for Subcommand { |
86 | fn default() -> Subcommand { | |
87 | Subcommand::Build { | |
88 | paths: vec![PathBuf::from("nowhere")], | |
89 | } | |
90 | } | |
91 | } | |
92 | ||
7453a54e SL |
93 | impl Flags { |
94 | pub fn parse(args: &[String]) -> Flags { | |
cc61c64b | 95 | let mut extra_help = String::new(); |
94b46f34 XL |
96 | let mut subcommand_help = format!( |
97 | "\ | |
cc61c64b XL |
98 | Usage: x.py <subcommand> [options] [<paths>...] |
99 | ||
100 | Subcommands: | |
101 | build Compile either the compiler or libraries | |
2c00a5a8 | 102 | check Compile either the compiler or libraries, using cargo check |
cc61c64b XL |
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 | |
7cac9316 XL |
107 | dist Build distribution artifacts |
108 | install Install distribution artifacts | |
cc61c64b | 109 | |
94b46f34 XL |
110 | To learn more about a subcommand, run `./x.py <subcommand> -h`" |
111 | ); | |
cc61c64b | 112 | |
7453a54e | 113 | let mut opts = Options::new(); |
cc61c64b | 114 | // Options common to all subcommands |
32a655c1 SL |
115 | opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)"); |
116 | opts.optflag("i", "incremental", "use incremental compilation"); | |
7453a54e | 117 | opts.optopt("", "config", "TOML configuration file for build", "FILE"); |
c30ab7b3 | 118 | opts.optopt("", "build", "build target of the stage0 compiler", "BUILD"); |
7453a54e | 119 | opts.optmulti("", "host", "host targets to build", "HOST"); |
c30ab7b3 | 120 | opts.optmulti("", "target", "target targets to build", "TARGET"); |
2c00a5a8 | 121 | opts.optmulti("", "exclude", "build paths to exclude", "PATH"); |
8bb4bdeb | 122 | opts.optopt("", "on-fail", "command to run on failure", "CMD"); |
83c7162d | 123 | opts.optflag("", "dry-run", "dry run; don't build anything"); |
7453a54e | 124 | opts.optopt("", "stage", "stage to build", "N"); |
476ff2be | 125 | opts.optopt("", "keep-stage", "stage to keep without recompiling", "N"); |
c30ab7b3 | 126 | opts.optopt("", "src", "path to the root of the rust checkout", "DIR"); |
7453a54e | 127 | opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS"); |
7453a54e | 128 | opts.optflag("h", "help", "print this help message"); |
94b46f34 XL |
129 | opts.optopt( |
130 | "", | |
131 | "warnings", | |
132 | "if value is deny, will deny warnings, otherwise use default", | |
133 | "VALUE", | |
134 | ); | |
0531ce1d | 135 | opts.optopt("", "error-format", "rustc error format", "FORMAT"); |
7453a54e | 136 | |
cc61c64b | 137 | // fn usage() |
94b46f34 XL |
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 | }; | |
cc61c64b XL |
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. | |
94b46f34 | 152 | let subcommand = args.iter().find(|&s| { |
041b39d2 | 153 | (s == "build") |
94b46f34 XL |
154 | || (s == "check") |
155 | || (s == "test") | |
156 | || (s == "bench") | |
157 | || (s == "doc") | |
158 | || (s == "clean") | |
159 | || (s == "dist") | |
160 | || (s == "install") | |
161 | }); | |
041b39d2 | 162 | let subcommand = match subcommand { |
cc61c64b XL |
163 | Some(s) => s, |
164 | None => { | |
abe05a73 XL |
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. | |
cc61c64b | 168 | println!("{}\n", subcommand_help); |
abe05a73 XL |
169 | let exit_code = if args.is_empty() { 0 } else { 1 }; |
170 | process::exit(exit_code); | |
cc61c64b XL |
171 | } |
172 | }; | |
c30ab7b3 | 173 | |
cc61c64b XL |
174 | // Some subcommands get extra options |
175 | match subcommand.as_str() { | |
94b46f34 | 176 | "test" => { |
7cac9316 XL |
177 | opts.optflag("", "no-fail-fast", "Run all tests regardless of failure"); |
178 | opts.optmulti("", "test-args", "extra arguments", "ARGS"); | |
2c00a5a8 XL |
179 | opts.optmulti( |
180 | "", | |
181 | "rustc-args", | |
182 | "extra options to pass the compiler when running tests", | |
183 | "ARGS", | |
184 | ); | |
83c7162d XL |
185 | opts.optflag("", "no-doc", "do not run doc tests"); |
186 | opts.optflag("", "doc", "only run doc tests"); | |
94b46f34 XL |
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 | _ => {} | |
cc61c64b XL |
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) => { | |
041b39d2 | 222 | if check_subcommand != subcommand { |
cc61c64b XL |
223 | pass_sanity_check = false; |
224 | } | |
94b46f34 | 225 | } |
cc61c64b XL |
226 | None => { |
227 | pass_sanity_check = false; | |
228 | } | |
229 | } | |
230 | if !pass_sanity_check { | |
231 | println!("{}\n", subcommand_help); | |
94b46f34 XL |
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 | ); | |
cc61c64b XL |
236 | process::exit(1); |
237 | } | |
238 | // Extra help text for some commands | |
239 | match subcommand.as_str() { | |
240 | "build" => { | |
94b46f34 XL |
241 | subcommand_help.push_str( |
242 | "\n | |
c30ab7b3 | 243 | Arguments: |
cc61c64b XL |
244 | This subcommand accepts a number of paths to directories to the crates |
245 | and/or artifacts to compile. For example: | |
c30ab7b3 SL |
246 | |
247 | ./x.py build src/libcore | |
cc61c64b | 248 | ./x.py build src/libcore src/libproc_macro |
c30ab7b3 SL |
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 | ||
041b39d2 XL |
257 | For a quick build of a usable compiler, you can pass: |
258 | ||
259 | ./x.py build --stage 1 src/libtest | |
c30ab7b3 | 260 | |
041b39d2 XL |
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. | |
94b46f34 XL |
264 | Once this is done, build/$ARCH/stage1 contains a usable compiler.", |
265 | ); | |
2c00a5a8 XL |
266 | } |
267 | "check" => { | |
94b46f34 XL |
268 | subcommand_help.push_str( |
269 | "\n | |
2c00a5a8 XL |
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 | |
94b46f34 XL |
281 | the compiler.", |
282 | ); | |
cc61c64b XL |
283 | } |
284 | "test" => { | |
94b46f34 XL |
285 | subcommand_help.push_str( |
286 | "\n | |
c30ab7b3 | 287 | Arguments: |
cc61c64b XL |
288 | This subcommand accepts a number of paths to directories to tests that |
289 | should be compiled and run. For example: | |
c30ab7b3 SL |
290 | |
291 | ./x.py test src/test/run-pass | |
c30ab7b3 SL |
292 | ./x.py test src/libstd --test-args hash_map |
293 | ./x.py test src/libstd --stage 0 | |
94b46f34 XL |
294 | ./x.py test src/test/ui --bless |
295 | ./x.py test src/test/ui --compare-mode nll | |
c30ab7b3 SL |
296 | |
297 | If no arguments are passed then the complete artifacts for that stage are | |
298 | compiled and tested. | |
299 | ||
300 | ./x.py test | |
94b46f34 XL |
301 | ./x.py test --stage 1", |
302 | ); | |
cc61c64b XL |
303 | } |
304 | "doc" => { | |
94b46f34 XL |
305 | subcommand_help.push_str( |
306 | "\n | |
c30ab7b3 | 307 | Arguments: |
cc61c64b XL |
308 | This subcommand accepts a number of paths to directories of documentation |
309 | to build. For example: | |
c30ab7b3 SL |
310 | |
311 | ./x.py doc src/doc/book | |
312 | ./x.py doc src/doc/nomicon | |
cc61c64b | 313 | ./x.py doc src/doc/book src/libstd |
c30ab7b3 SL |
314 | |
315 | If no arguments are passed then everything is documented: | |
316 | ||
317 | ./x.py doc | |
94b46f34 XL |
318 | ./x.py doc --stage 1", |
319 | ); | |
c30ab7b3 | 320 | } |
94b46f34 | 321 | _ => {} |
7453a54e | 322 | }; |
cc61c64b | 323 | // Get any optional paths which occur after the subcommand |
94b46f34 XL |
324 | let paths = matches.free[1..] |
325 | .iter() | |
326 | .map(|p| p.into()) | |
327 | .collect::<Vec<PathBuf>>(); | |
cc61c64b | 328 | |
7cac9316 XL |
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 | }); | |
cc61c64b | 336 | |
ea8adc8c | 337 | // All subcommands except `clean` can have an optional "Available paths" section |
cc61c64b | 338 | if matches.opt_present("verbose") { |
3b2f2976 XL |
339 | let config = Config::parse(&["build".to_string()]); |
340 | let mut build = Build::new(config); | |
cc61c64b | 341 | metadata::build(&mut build); |
3b2f2976 XL |
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()); | |
ea8adc8c | 345 | } else if subcommand.as_str() != "clean" { |
94b46f34 XL |
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 | ); | |
cc61c64b | 352 | } |
c30ab7b3 | 353 | |
cc61c64b XL |
354 | // User passed in -h/--help? |
355 | if matches.opt_present("help") { | |
356 | usage(0, &opts, &subcommand_help, &extra_help); | |
357 | } | |
c30ab7b3 | 358 | |
cc61c64b | 359 | let cmd = match subcommand.as_str() { |
94b46f34 XL |
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 }, | |
c30ab7b3 | 382 | "clean" => { |
cc61c64b | 383 | if paths.len() > 0 { |
ea8adc8c | 384 | println!("\nclean does not take a path argument\n"); |
cc61c64b | 385 | usage(1, &opts, &subcommand_help, &extra_help); |
c30ab7b3 | 386 | } |
ea8adc8c XL |
387 | |
388 | Subcommand::Clean { | |
389 | all: matches.opt_present("all"), | |
390 | } | |
c30ab7b3 | 391 | } |
94b46f34 XL |
392 | "dist" => Subcommand::Dist { paths }, |
393 | "install" => Subcommand::Install { paths }, | |
cc61c64b XL |
394 | _ => { |
395 | usage(1, &opts, &subcommand_help, &extra_help); | |
c30ab7b3 SL |
396 | } |
397 | }; | |
398 | ||
7453a54e | 399 | Flags { |
cc61c64b | 400 | verbose: matches.opt_count("verbose"), |
83c7162d XL |
401 | stage: matches.opt_str("stage").map(|j| j.parse().unwrap()), |
402 | dry_run: matches.opt_present("dry-run"), | |
cc61c64b | 403 | on_fail: matches.opt_str("on-fail"), |
0531ce1d | 404 | rustc_error_format: matches.opt_str("error-format"), |
cc61c64b | 405 | keep_stage: matches.opt_str("keep-stage").map(|j| j.parse().unwrap()), |
3b2f2976 | 406 | host: split(matches.opt_strs("host")) |
94b46f34 XL |
407 | .into_iter() |
408 | .map(|x| INTERNER.intern_string(x)) | |
409 | .collect::<Vec<_>>(), | |
3b2f2976 | 410 | target: split(matches.opt_strs("target")) |
94b46f34 XL |
411 | .into_iter() |
412 | .map(|x| INTERNER.intern_string(x)) | |
413 | .collect::<Vec<_>>(), | |
7453a54e | 414 | config: cfg_file, |
cc61c64b | 415 | jobs: matches.opt_str("jobs").map(|j| j.parse().unwrap()), |
3b2f2976 | 416 | cmd, |
cc61c64b | 417 | incremental: matches.opt_present("incremental"), |
2c00a5a8 | 418 | exclude: split(matches.opt_strs("exclude")) |
94b46f34 XL |
419 | .into_iter() |
420 | .map(|p| p.into()) | |
421 | .collect::<Vec<_>>(), | |
83c7162d | 422 | warnings: matches.opt_str("warnings").map(|v| v == "deny"), |
7453a54e SL |
423 | } |
424 | } | |
425 | } | |
426 | ||
c30ab7b3 SL |
427 | impl Subcommand { |
428 | pub fn test_args(&self) -> Vec<&str> { | |
429 | match *self { | |
94b46f34 XL |
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() | |
c30ab7b3 SL |
435 | } |
436 | _ => Vec::new(), | |
437 | } | |
7453a54e | 438 | } |
7cac9316 | 439 | |
2c00a5a8 XL |
440 | pub fn rustc_args(&self) -> Vec<&str> { |
441 | match *self { | |
94b46f34 XL |
442 | Subcommand::Test { ref rustc_args, .. } => rustc_args |
443 | .iter() | |
444 | .flat_map(|s| s.split_whitespace()) | |
445 | .collect(), | |
2c00a5a8 XL |
446 | _ => Vec::new(), |
447 | } | |
448 | } | |
449 | ||
041b39d2 | 450 | pub fn fail_fast(&self) -> bool { |
7cac9316 | 451 | match *self { |
041b39d2 | 452 | Subcommand::Test { fail_fast, .. } => fail_fast, |
7cac9316 XL |
453 | _ => false, |
454 | } | |
455 | } | |
0531ce1d | 456 | |
83c7162d | 457 | pub fn doc_tests(&self) -> DocTests { |
0531ce1d XL |
458 | match *self { |
459 | Subcommand::Test { doc_tests, .. } => doc_tests, | |
83c7162d | 460 | _ => DocTests::Yes, |
0531ce1d XL |
461 | } |
462 | } | |
94b46f34 XL |
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 | } | |
7453a54e | 479 | } |
32a655c1 SL |
480 | |
481 | fn split(s: Vec<String>) -> Vec<String> { | |
94b46f34 XL |
482 | s.iter() |
483 | .flat_map(|s| s.split(',')) | |
484 | .map(|s| s.to_string()) | |
485 | .collect() | |
32a655c1 | 486 | } |