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