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