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