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