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