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