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