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