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