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