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