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