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