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