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