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