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