From 837b550238aa671a591ccf282dddeab29cadb206 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 18 May 2024 04:49:42 +0200 Subject: Merging upstream version 1.71.1+dfsg1. Signed-off-by: Daniel Baumann --- src/bootstrap/flags.rs | 1033 +++++++++++++++++------------------------------- 1 file changed, 358 insertions(+), 675 deletions(-) (limited to 'src/bootstrap/flags.rs') diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs index b6f5f3103..80e715777 100644 --- a/src/bootstrap/flags.rs +++ b/src/bootstrap/flags.rs @@ -3,726 +3,415 @@ //! This module implements the command-line parsing of the build system which //! has various flags to configure how it's run. -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use getopts::Options; +use clap::{CommandFactory, Parser, ValueEnum}; use crate::builder::{Builder, Kind}; -use crate::config::{Config, TargetSelection}; +use crate::config::{target_selection_list, Config, TargetSelectionList}; use crate::setup::Profile; -use crate::util::t; use crate::{Build, DocTests}; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default, Debug, ValueEnum)] pub enum Color { Always, Never, + #[default] Auto, } -impl Default for Color { - fn default() -> Self { - Self::Auto - } -} - -impl std::str::FromStr for Color { - type Err = (); - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "always" => Ok(Self::Always), - "never" => Ok(Self::Never), - "auto" => Ok(Self::Auto), - _ => Err(()), - } - } +/// Whether to deny warnings, emit them as warnings, or use the default behavior +#[derive(Copy, Clone, Default, Debug, ValueEnum)] +pub enum Warnings { + Deny, + Warn, + #[default] + Default, } /// Deserialized version of all flags for this compile. +#[derive(Debug, Parser)] +#[clap( + override_usage = "x.py [options] [...]", + disable_help_subcommand(true), + about = "", + next_line_help(false) +)] pub struct Flags { - pub verbose: usize, // number of -v args; each extra -v after the first is passed to Cargo - pub on_fail: Option, - pub stage: Option, - pub keep_stage: Vec, - pub keep_stage_std: Vec, + #[command(subcommand)] + pub cmd: Subcommand, - pub host: Option>, - pub target: Option>, + #[arg(global(true), short, long, action = clap::ArgAction::Count)] + /// use verbose output (-vv for very verbose) + pub verbose: u8, // each extra -v after the first is passed to Cargo + #[arg(global(true), short, long)] + /// use incremental compilation + pub incremental: bool, + #[arg(global(true), long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")] + /// TOML configuration file for build pub config: Option, + #[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")] + /// Build directory, overrides `build.build-dir` in `config.toml` pub build_dir: Option, - pub jobs: Option, - pub cmd: Subcommand, - pub incremental: bool, + + #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "BUILD")] + /// build target of the stage0 compiler + pub build: Option, + + #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)] + /// host targets to build + pub host: Option, + + #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)] + /// target targets to build + pub target: Option, + + #[arg(global(true), long, value_name = "PATH")] + /// build paths to exclude pub exclude: Vec, + #[arg(global(true), long)] + /// include default paths in addition to the provided ones pub include_default_paths: bool, + + #[arg(global(true), value_hint = clap::ValueHint::Other, long)] pub rustc_error_format: Option, - pub json_output: bool, + + #[arg(global(true), long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")] + /// command to run on failure + pub on_fail: Option, + #[arg(global(true), long)] + /// dry run; don't build anything pub dry_run: bool, - pub color: Color, + #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")] + /// stage to build (indicates compiler to use/test, e.g., stage 0 uses the + /// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.) + pub stage: Option, + #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")] + /// stage(s) to keep without recompiling + /// (pass multiple times to keep e.g., both stages 0 and 1) + pub keep_stage: Vec, + #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")] + /// stage(s) of the standard library to keep without recompiling + /// (pass multiple times to keep e.g., both stages 0 and 1) + pub keep_stage_std: Vec, + #[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")] + /// path to the root of the rust checkout + pub src: Option, + + #[arg( + global(true), + short, + long, + value_hint = clap::ValueHint::Other, + default_value_t = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get), + value_name = "JOBS" + )] + /// number of jobs to run in parallel + pub jobs: usize, // This overrides the deny-warnings configuration option, // which passes -Dwarnings to the compiler invocations. - // - // true => deny, false => warn - pub deny_warnings: Option, + #[arg(global(true), long)] + #[clap(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")] + /// if value is deny, will deny warnings + /// if value is warn, will emit warnings + /// otherwise, use the default configured behaviour + pub warnings: Warnings, + + #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")] + /// rustc error format + pub error_format: Option, + #[arg(global(true), long)] + /// use message-format=json + pub json_output: bool, - pub rust_profile_use: Option, - pub rust_profile_generate: Option, + #[arg(global(true), long, value_name = "STYLE")] + #[clap(value_enum, default_value_t = Color::Auto)] + /// whether to use color in cargo and rustc output + pub color: Color, + /// whether rebuilding llvm should be skipped, overriding `skip-rebuld` in config.toml + #[arg(global(true), long, value_name = "VALUE")] + pub llvm_skip_rebuild: Option, + /// generate PGO profile with rustc build + #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] + pub rust_profile_generate: Option, + /// use PGO profile for rustc build + #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] + pub rust_profile_use: Option, + /// use PGO profile for LLVM build + #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] pub llvm_profile_use: Option, // LLVM doesn't support a custom location for generating profile // information. // // llvm_out/build/profiles/ is the location this writes to. + /// generate PGO profile with llvm built for rustc + #[arg(global(true), long)] pub llvm_profile_generate: bool, + /// generate BOLT profile for LLVM build + #[arg(global(true), long)] pub llvm_bolt_profile_generate: bool, + /// use BOLT profile for LLVM build + #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] pub llvm_bolt_profile_use: Option, + #[arg(global(true))] + /// paths for the subcommand + pub paths: Vec, + /// override options in config.toml + #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")] + pub set: Vec, + /// arguments passed to subcommands + #[arg(global(true), last(true), value_name = "ARGS")] + pub free_args: Vec, +} + +impl Flags { + pub fn parse(args: &[String]) -> Self { + let first = String::from("x.py"); + let it = std::iter::once(&first).chain(args.iter()); + // We need to check for ` -h -v`, in which case we list the paths + #[derive(Parser)] + #[clap(disable_help_flag(true))] + struct HelpVerboseOnly { + #[arg(short, long)] + help: bool, + #[arg(global(true), short, long, action = clap::ArgAction::Count)] + pub verbose: u8, + #[arg(value_enum)] + cmd: Kind, + } + if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) = + HelpVerboseOnly::try_parse_from(it.clone()) + { + println!("note: updating submodules before printing available paths"); + let config = Config::parse(&[String::from("build")]); + let build = Build::new(config); + let paths = Builder::get_help(&build, subcommand); + if let Some(s) = paths { + println!("{}", s); + } else { + panic!("No paths available for subcommand `{}`", subcommand.as_str()); + } + crate::detail_exit(0); + } - /// Arguments appearing after `--` to be forwarded to tools, - /// e.g. `--fix-broken` or test arguments. - pub free_args: Option>, + Flags::parse_from(it) + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default, clap::Subcommand)] pub enum Subcommand { - Build { - paths: Vec, - }, + #[clap(aliases = ["b"], long_about = "\n + Arguments: + This subcommand accepts a number of paths to directories to the crates + and/or artifacts to compile. For example, for a quick build of a usable + compiler: + ./x.py build --stage 1 library/std + This will build a compiler and standard library from the local source code. + Once this is done, build/$ARCH/stage1 contains a usable compiler. + If no arguments are passed then the default artifacts for that stage are + compiled. For example: + ./x.py build --stage 0 + ./x.py build ")] + /// Compile either the compiler or libraries + #[default] + Build, + #[clap(aliases = ["c"], long_about = "\n + Arguments: + This subcommand accepts a number of paths to directories to the crates + and/or artifacts to compile. For example: + ./x.py check library/std + If no arguments are passed then many artifacts are checked.")] + /// Compile either the compiler or libraries, using cargo check Check { - paths: Vec, + #[arg(long)] + /// Check all targets + all_targets: bool, }, + /// Run Clippy (uses rustup/cargo-installed clippy binary) + #[clap(long_about = "\n + Arguments: + This subcommand accepts a number of paths to directories to the crates + and/or artifacts to run clippy against. For example: + ./x.py clippy library/core + ./x.py clippy library/core library/proc_macro")] Clippy { + #[arg(long)] fix: bool, - paths: Vec, - clippy_lint_allow: Vec, - clippy_lint_deny: Vec, - clippy_lint_warn: Vec, - clippy_lint_forbid: Vec, - }, - Fix { - paths: Vec, + /// clippy lints to allow + #[arg(global(true), short = 'A', action = clap::ArgAction::Append, value_name = "LINT")] + allow: Vec, + /// clippy lints to deny + #[arg(global(true), short = 'D', action = clap::ArgAction::Append, value_name = "LINT")] + deny: Vec, + /// clippy lints to warn on + #[arg(global(true), short = 'W', action = clap::ArgAction::Append, value_name = "LINT")] + warn: Vec, + /// clippy lints to forbid + #[arg(global(true), short = 'F', action = clap::ArgAction::Append, value_name = "LINT")] + forbid: Vec, }, + /// Run cargo fix + #[clap(long_about = "\n + Arguments: + This subcommand accepts a number of paths to directories to the crates + and/or artifacts to run `cargo fix` against. For example: + ./x.py fix library/core + ./x.py fix library/core library/proc_macro")] + Fix, + #[clap( + name = "fmt", + long_about = "\n + Arguments: + This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and + fails if it is not. For example: + ./x.py fmt + ./x.py fmt --check" + )] + /// Run rustfmt Format { - paths: Vec, + /// check formatting instead of applying + #[arg(long)] check: bool, }, + #[clap(aliases = ["d"], long_about = "\n + Arguments: + This subcommand accepts a number of paths to directories of documentation + to build. For example: + ./x.py doc src/doc/book + ./x.py doc src/doc/nomicon + ./x.py doc src/doc/book library/std + ./x.py doc library/std --json + ./x.py doc library/std --open + If no arguments are passed then everything is documented: + ./x.py doc + ./x.py doc --stage 1")] + /// Build documentation Doc { - paths: Vec, + #[arg(long)] + /// open the docs in a browser open: bool, + #[arg(long)] + /// render the documentation in JSON format in addition to the usual HTML format json: bool, }, + #[clap(aliases = ["t"], long_about = "\n + Arguments: + This subcommand accepts a number of paths to test directories that + should be compiled and run. For example: + ./x.py test tests/ui + ./x.py test library/std --test-args hash_map + ./x.py test library/std --stage 0 --no-doc + ./x.py test tests/ui --bless + ./x.py test tests/ui --compare-mode chalk + Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`; + just like `build library/std --stage N` it tests the compiler produced by the previous + stage. + Execute tool tests with a tool name argument: + ./x.py test tidy + If no arguments are passed then the complete artifacts for that stage are + compiled and tested. + ./x.py test + ./x.py test --stage 1")] + /// Build and run some test suites Test { - paths: Vec, - /// Whether to automatically update stderr/stdout files + #[arg(long)] + /// run all tests regardless of failure + no_fail_fast: bool, + #[arg(long, value_name = "SUBSTRING")] + /// skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times + skip: Vec, + #[arg(long, value_name = "ARGS", allow_hyphen_values(true))] + /// extra arguments to be passed for the test tool being used + /// (e.g. libtest, compiletest or rustdoc) + test_args: Vec, + /// extra options to pass the compiler when running tests + #[arg(long, value_name = "ARGS", allow_hyphen_values(true))] + rustc_args: Vec, + #[arg(long)] + /// do not run doc tests + no_doc: bool, + #[arg(long)] + /// only run doc tests + doc: bool, + #[arg(long)] + /// whether to automatically update stderr/stdout files bless: bool, + #[arg(long)] + /// rerun tests even if the inputs are unchanged force_rerun: bool, + #[arg(long)] + /// only run tests that result has been changed + only_modified: bool, + #[arg(long, value_name = "COMPARE MODE")] + /// mode describing what file the actual ui output will be compared to compare_mode: Option, + #[arg(long, value_name = "check | build | run")] + /// force {check,build,run}-pass tests to this mode. pass: Option, + #[arg(long, value_name = "auto | always | never")] + /// whether to execute run-* tests run: Option, - test_args: Vec, - rustc_args: Vec, - fail_fast: bool, - doc_tests: DocTests, + #[arg(long)] + /// enable this to generate a Rustfix coverage file, which is saved in + /// `//rustfix_missing_coverage.txt` rustfix_coverage: bool, - only_modified: bool, }, + /// Build and run some benchmarks Bench { - paths: Vec, + #[arg(long, allow_hyphen_values(true))] test_args: Vec, }, + /// Clean out build directories Clean { - paths: Vec, + #[arg(long)] all: bool, }, - Dist { - paths: Vec, - }, - Install { - paths: Vec, - }, + /// Build distribution artifacts + Dist, + /// Install distribution artifacts + Install, + #[clap(aliases = ["r"], long_about = "\n + Arguments: + This subcommand accepts a number of paths to tools to build and run. For + example: + ./x.py run src/tools/expand-yaml-anchors + At least a tool needs to be called.")] + /// Run tools contained in this repository Run { - paths: Vec, + /// arguments for the tool + #[arg(long, allow_hyphen_values(true))] args: Vec, }, - Setup { - profile: Option, - }, - Suggest { - run: bool, - }, -} - -impl Default for Subcommand { - fn default() -> Subcommand { - Subcommand::Build { paths: vec![PathBuf::from("nowhere")] } - } -} - -impl Flags { - pub fn parse(args: &[String]) -> Flags { - let (args, free_args) = if let Some(pos) = args.iter().position(|s| s == "--") { - let (args, free) = args.split_at(pos); - (args, Some(free[1..].to_vec())) - } else { - (args, None) - }; - let mut subcommand_help = String::from( - "\ -Usage: x.py [options] [...] - -Subcommands: - build, b Compile either the compiler or libraries - check, c Compile either the compiler or libraries, using cargo check - clippy Run clippy (uses rustup/cargo-installed clippy binary) - fix Run cargo fix - fmt Run rustfmt - test, t Build and run some test suites - bench Build and run some benchmarks - doc, d Build documentation - clean Clean out build directories - dist Build distribution artifacts - install Install distribution artifacts - run, r Run tools contained in this repository - setup Create a config.toml (making it easier to use `x.py` itself) - suggest Suggest a subset of tests to run, based on modified files - -To learn more about a subcommand, run `./x.py -h`", - ); - - let mut opts = Options::new(); - // Options common to all subcommands - opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)"); - opts.optflag("i", "incremental", "use incremental compilation"); - opts.optopt("", "config", "TOML configuration file for build", "FILE"); - opts.optopt( - "", - "build-dir", - "Build directory, overrides `build.build-dir` in `config.toml`", - "DIR", - ); - opts.optopt("", "build", "build target of the stage0 compiler", "BUILD"); - opts.optmulti("", "host", "host targets to build", "HOST"); - opts.optmulti("", "target", "target targets to build", "TARGET"); - opts.optmulti("", "exclude", "build paths to exclude", "PATH"); - opts.optflag( - "", - "include-default-paths", - "include default paths in addition to the provided ones", - ); - opts.optopt("", "on-fail", "command to run on failure", "CMD"); - opts.optflag("", "dry-run", "dry run; don't build anything"); - opts.optopt( - "", - "stage", - "stage to build (indicates compiler to use/test, e.g., stage 0 uses the \ - bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)", - "N", - ); - opts.optmulti( - "", - "keep-stage", - "stage(s) to keep without recompiling \ - (pass multiple times to keep e.g., both stages 0 and 1)", - "N", - ); - opts.optmulti( - "", - "keep-stage-std", - "stage(s) of the standard library to keep without recompiling \ - (pass multiple times to keep e.g., both stages 0 and 1)", - "N", - ); - opts.optopt("", "src", "path to the root of the rust checkout", "DIR"); - let j_msg = format!( - "number of jobs to run in parallel; \ - defaults to {} (this host's logical CPU count)", - std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) - ); - opts.optopt("j", "jobs", &j_msg, "JOBS"); - opts.optflag("h", "help", "print this help message"); - opts.optopt( - "", - "warnings", - "if value is deny, will deny warnings, otherwise use default", - "VALUE", - ); - opts.optopt("", "error-format", "rustc error format", "FORMAT"); - opts.optflag("", "json-output", "use message-format=json"); - opts.optopt("", "color", "whether to use color in cargo and rustc output", "STYLE"); - opts.optopt( - "", - "rust-profile-generate", - "generate PGO profile with rustc build", - "PROFILE", - ); - opts.optopt("", "rust-profile-use", "use PGO profile for rustc build", "PROFILE"); - opts.optflag("", "llvm-profile-generate", "generate PGO profile with llvm built for rustc"); - opts.optopt("", "llvm-profile-use", "use PGO profile for llvm build", "PROFILE"); - opts.optmulti("A", "", "allow certain clippy lints", "OPT"); - opts.optmulti("D", "", "deny certain clippy lints", "OPT"); - opts.optmulti("W", "", "warn about certain clippy lints", "OPT"); - opts.optmulti("F", "", "forbid certain clippy lints", "OPT"); - opts.optflag("", "llvm-bolt-profile-generate", "generate BOLT profile for LLVM build"); - opts.optopt("", "llvm-bolt-profile-use", "use BOLT profile for LLVM build", "PROFILE"); - - // We can't use getopt to parse the options until we have completed specifying which - // options are valid, but under the current implementation, some options are conditional on - // the subcommand. Therefore we must manually identify the subcommand first, so that we can - // complete the definition of the options. Then we can use the getopt::Matches object from - // there on out. - let subcommand = match args.iter().find_map(|s| Kind::parse(&s)) { - Some(s) => s, - None => { - // No or an invalid subcommand -- show the general usage and subcommand help - // An exit code will be 0 when no subcommand is given, and 1 in case of an invalid - // subcommand. - println!("{}\n", subcommand_help); - let exit_code = if args.is_empty() { 0 } else { 1 }; - crate::detail_exit(exit_code); - } - }; - - // Some subcommands get extra options - match subcommand { - Kind::Test => { - opts.optflag("", "no-fail-fast", "Run all tests regardless of failure"); - opts.optmulti("", "skip", "skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times", "SUBSTRING"); - opts.optmulti( - "", - "test-args", - "extra arguments to be passed for the test tool being used \ - (e.g. libtest, compiletest or rustdoc)", - "ARGS", - ); - opts.optmulti( - "", - "rustc-args", - "extra options to pass the compiler when running tests", - "ARGS", - ); - opts.optflag("", "no-doc", "do not run doc tests"); - opts.optflag("", "doc", "only run doc tests"); - opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests"); - opts.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged"); - opts.optflag("", "only-modified", "only run tests that result has been changed"); - opts.optopt( - "", - "compare-mode", - "mode describing what file the actual ui output will be compared to", - "COMPARE MODE", - ); - opts.optopt( - "", - "pass", - "force {check,build,run}-pass tests to this mode.", - "check | build | run", - ); - opts.optopt("", "run", "whether to execute run-* tests", "auto | always | never"); - opts.optflag( - "", - "rustfix-coverage", - "enable this to generate a Rustfix coverage file, which is saved in \ - `//rustfix_missing_coverage.txt`", - ); - } - Kind::Check => { - opts.optflag("", "all-targets", "Check all targets"); - } - Kind::Bench => { - opts.optmulti("", "test-args", "extra arguments", "ARGS"); - } - Kind::Clippy => { - opts.optflag("", "fix", "automatically apply lint suggestions"); - } - Kind::Doc => { - opts.optflag("", "open", "open the docs in a browser"); - opts.optflag( - "", - "json", - "render the documentation in JSON format in addition to the usual HTML format", - ); - } - Kind::Clean => { - opts.optflag("", "all", "clean all build artifacts"); - } - Kind::Format => { - opts.optflag("", "check", "check formatting instead of applying."); - } - Kind::Run => { - opts.optmulti("", "args", "arguments for the tool", "ARGS"); - } - Kind::Suggest => { - opts.optflag("", "run", "run suggested tests"); - } - _ => {} - }; - - // fn usage() - let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! { - println!("{}", opts.usage(subcommand_help)); - if verbose { - // We have an unfortunate situation here: some Steps use `builder.in_tree_crates` to determine their paths. - // To determine those crates, we need to run `cargo metadata`, which means we need all submodules to be checked out. - // That takes a while to run, so only do it when paths were explicitly requested, not on all CLI errors. - // `Build::new` won't load submodules for the `setup` command. - let cmd = if verbose { - println!("note: updating submodules before printing available paths"); - "build" - } else { - "setup" - }; - let config = Config::parse(&[cmd.to_string()]); - let build = Build::new(config); - let paths = Builder::get_help(&build, subcommand); - - if let Some(s) = paths { - println!("{}", s); - } else { - panic!("No paths available for subcommand `{}`", subcommand.as_str()); - } - } else { - println!( - "Run `./x.py {} -h -v` to see a list of available paths.", - subcommand.as_str() - ); - } - crate::detail_exit(exit_code); - }; - - // Done specifying what options are possible, so do the getopts parsing - let matches = opts.parse(args).unwrap_or_else(|e| { - // Invalid argument/option format - println!("\n{}\n", e); - usage(1, &opts, false, &subcommand_help); - }); - - // Extra sanity check to make sure we didn't hit this crazy corner case: - // - // ./x.py --frobulate clean build - // ^-- option ^ ^- actual subcommand - // \_ arg to option could be mistaken as subcommand - let mut pass_sanity_check = true; - match matches.free.get(0).and_then(|s| Kind::parse(&s)) { - Some(check_subcommand) => { - if check_subcommand != subcommand { - pass_sanity_check = false; - } - } - None => { - pass_sanity_check = false; - } - } - if !pass_sanity_check { - eprintln!("{}\n", subcommand_help); - eprintln!( - "Sorry, I couldn't figure out which subcommand you were trying to specify.\n\ - You may need to move some options to after the subcommand.\n" - ); - crate::detail_exit(1); - } - // Extra help text for some commands - match subcommand { - Kind::Build => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to directories to the crates - and/or artifacts to compile. For example, for a quick build of a usable - compiler: - - ./x.py build --stage 1 library/std - - This will build a compiler and standard library from the local source code. - Once this is done, build/$ARCH/stage1 contains a usable compiler. - - If no arguments are passed then the default artifacts for that stage are - compiled. For example: - - ./x.py build --stage 0 - ./x.py build ", - ); - } - Kind::Check => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to directories to the crates - and/or artifacts to compile. For example: - - ./x.py check library/std - - If no arguments are passed then many artifacts are checked.", - ); - } - Kind::Clippy => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to directories to the crates - and/or artifacts to run clippy against. For example: - - ./x.py clippy library/core - ./x.py clippy library/core library/proc_macro", - ); - } - Kind::Fix => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to directories to the crates - and/or artifacts to run `cargo fix` against. For example: - - ./x.py fix library/core - ./x.py fix library/core library/proc_macro", - ); - } - Kind::Format => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand optionally accepts a `--check` flag which succeeds if formatting is correct and - fails if it is not. For example: - - ./x.py fmt - ./x.py fmt --check", - ); - } - Kind::Test => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to test directories that - should be compiled and run. For example: - - ./x.py test tests/ui - ./x.py test library/std --test-args hash_map - ./x.py test library/std --stage 0 --no-doc - ./x.py test tests/ui --bless - ./x.py test tests/ui --compare-mode chalk - - Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`; - just like `build library/std --stage N` it tests the compiler produced by the previous - stage. - - Execute tool tests with a tool name argument: - - ./x.py test tidy - - If no arguments are passed then the complete artifacts for that stage are - compiled and tested. - - ./x.py test - ./x.py test --stage 1", - ); - } - Kind::Doc => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to directories of documentation - to build. For example: - - ./x.py doc src/doc/book - ./x.py doc src/doc/nomicon - ./x.py doc src/doc/book library/std - ./x.py doc library/std --json - ./x.py doc library/std --open - - If no arguments are passed then everything is documented: - - ./x.py doc - ./x.py doc --stage 1", - ); - } - Kind::Run => { - subcommand_help.push_str( - "\n -Arguments: - This subcommand accepts a number of paths to tools to build and run. For - example: - - ./x.py run src/tools/expand-yaml-anchors - - At least a tool needs to be called.", - ); - } - Kind::Setup => { - subcommand_help.push_str(&format!( - "\n + /// Set up the environment for development + #[clap(long_about = format!( + "\n x.py setup creates a `config.toml` which changes the defaults for x.py itself, -as well as setting up a git pre-push hook, VS code config and toolchain link. - +as well as setting up a git pre-push hook, VS Code config and toolchain link. Arguments: This subcommand accepts a 'profile' to use for builds. For example: - ./x.py setup library - The profile is optional and you will be prompted interactively if it is not given. The following profiles are available: - {} - - To only set up the git hook, VS code or toolchain link, you may use + To only set up the git hook, VS Code config or toolchain link, you may use ./x.py setup hook ./x.py setup vscode - ./x.py setup link -", - Profile::all_for_help(" ").trim_end() - )); - } - Kind::Bench | Kind::Clean | Kind::Dist | Kind::Install | Kind::Suggest => {} - }; - // Get any optional paths which occur after the subcommand - let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::>(); - - let verbose = matches.opt_present("verbose"); - - // User passed in -h/--help? - if matches.opt_present("help") { - usage(0, &opts, verbose, &subcommand_help); - } - - let cmd = match subcommand { - Kind::Build => Subcommand::Build { paths }, - Kind::Check => { - if matches.opt_present("all-targets") { - println!( - "Warning: --all-targets is now on by default and does not need to be passed explicitly." - ); - } - Subcommand::Check { paths } - } - Kind::Clippy => Subcommand::Clippy { - paths, - fix: matches.opt_present("fix"), - clippy_lint_allow: matches.opt_strs("A"), - clippy_lint_warn: matches.opt_strs("W"), - clippy_lint_deny: matches.opt_strs("D"), - clippy_lint_forbid: matches.opt_strs("F"), - }, - Kind::Fix => Subcommand::Fix { paths }, - Kind::Test => Subcommand::Test { - paths, - bless: matches.opt_present("bless"), - force_rerun: matches.opt_present("force-rerun"), - compare_mode: matches.opt_str("compare-mode"), - pass: matches.opt_str("pass"), - run: matches.opt_str("run"), - test_args: matches.opt_strs("test-args"), - rustc_args: matches.opt_strs("rustc-args"), - fail_fast: !matches.opt_present("no-fail-fast"), - rustfix_coverage: matches.opt_present("rustfix-coverage"), - only_modified: matches.opt_present("only-modified"), - doc_tests: if matches.opt_present("doc") { - DocTests::Only - } else if matches.opt_present("no-doc") { - DocTests::No - } else { - DocTests::Yes - }, - }, - Kind::Bench => Subcommand::Bench { paths, test_args: matches.opt_strs("test-args") }, - Kind::Doc => Subcommand::Doc { - paths, - open: matches.opt_present("open"), - json: matches.opt_present("json"), - }, - Kind::Clean => Subcommand::Clean { all: matches.opt_present("all"), paths }, - Kind::Format => Subcommand::Format { check: matches.opt_present("check"), paths }, - Kind::Dist => Subcommand::Dist { paths }, - Kind::Install => Subcommand::Install { paths }, - Kind::Suggest => Subcommand::Suggest { run: matches.opt_present("run") }, - Kind::Run => { - if paths.is_empty() { - println!("\nrun requires at least a path!\n"); - usage(1, &opts, verbose, &subcommand_help); - } - Subcommand::Run { paths, args: matches.opt_strs("args") } - } - Kind::Setup => { - let profile = if paths.len() > 1 { - eprintln!("\nerror: At most one option can be passed to setup\n"); - usage(1, &opts, verbose, &subcommand_help) - } else if let Some(path) = paths.pop() { - let profile_string = t!(path.into_os_string().into_string().map_err( - |path| format!("{} is not a valid UTF8 string", path.to_string_lossy()) - )); - - let profile = profile_string.parse().unwrap_or_else(|err| { - eprintln!("error: {}", err); - eprintln!("help: the available profiles are:"); - eprint!("{}", Profile::all_for_help("- ")); - crate::detail_exit(1); - }); - Some(profile) - } else { - None - }; - Subcommand::Setup { profile } - } - }; - - Flags { - verbose: matches.opt_count("verbose"), - stage: matches.opt_str("stage").map(|j| j.parse().expect("`stage` should be a number")), - dry_run: matches.opt_present("dry-run"), - on_fail: matches.opt_str("on-fail"), - rustc_error_format: matches.opt_str("error-format"), - json_output: matches.opt_present("json-output"), - keep_stage: matches - .opt_strs("keep-stage") - .into_iter() - .map(|j| j.parse().expect("`keep-stage` should be a number")) - .collect(), - keep_stage_std: matches - .opt_strs("keep-stage-std") - .into_iter() - .map(|j| j.parse().expect("`keep-stage-std` should be a number")) - .collect(), - host: if matches.opt_present("host") { - Some( - split(&matches.opt_strs("host")) - .into_iter() - .map(|x| TargetSelection::from_user(&x)) - .collect::>(), - ) - } else { - None - }, - target: if matches.opt_present("target") { - Some( - split(&matches.opt_strs("target")) - .into_iter() - .map(|x| TargetSelection::from_user(&x)) - .collect::>(), - ) - } else { - None - }, - config: matches.opt_str("config").map(PathBuf::from), - build_dir: matches.opt_str("build-dir").map(PathBuf::from), - jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")), - cmd, - incremental: matches.opt_present("incremental"), - exclude: split(&matches.opt_strs("exclude")) - .into_iter() - .map(|p| p.into()) - .collect::>(), - include_default_paths: matches.opt_present("include-default-paths"), - deny_warnings: parse_deny_warnings(&matches), - color: matches - .opt_get_default("color", Color::Auto) - .expect("`color` should be `always`, `never`, or `auto`"), - rust_profile_use: matches.opt_str("rust-profile-use"), - rust_profile_generate: matches.opt_str("rust-profile-generate"), - llvm_profile_use: matches.opt_str("llvm-profile-use"), - llvm_profile_generate: matches.opt_present("llvm-profile-generate"), - llvm_bolt_profile_generate: matches.opt_present("llvm-bolt-profile-generate"), - llvm_bolt_profile_use: matches.opt_str("llvm-bolt-profile-use"), - free_args, - } - } + ./x.py setup link", Profile::all_for_help(" ").trim_end()))] + Setup { + /// Either the profile for `config.toml` or another setup action. + /// May be omitted to set up interactively + #[arg(value_name = "|hook|vscode|link")] + profile: Option, + }, + /// Suggest a subset of tests to run, based on modified files + #[clap(long_about = "\n")] + Suggest { + /// run suggested tests + #[arg(long)] + run: bool, + }, } impl Subcommand { @@ -745,15 +434,6 @@ impl Subcommand { } } - pub fn test_args(&self) -> Vec<&str> { - match *self { - Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => { - test_args.iter().flat_map(|s| s.split_whitespace()).collect() - } - _ => vec![], - } - } - pub fn rustc_args(&self) -> Vec<&str> { match *self { Subcommand::Test { ref rustc_args, .. } => { @@ -763,25 +443,24 @@ impl Subcommand { } } - pub fn args(&self) -> Vec<&str> { - match *self { - Subcommand::Run { ref args, .. } => { - args.iter().flat_map(|s| s.split_whitespace()).collect() - } - _ => vec![], - } - } - pub fn fail_fast(&self) -> bool { match *self { - Subcommand::Test { fail_fast, .. } => fail_fast, + Subcommand::Test { no_fail_fast, .. } => !no_fail_fast, _ => false, } } pub fn doc_tests(&self) -> DocTests { match *self { - Subcommand::Test { doc_tests, .. } => doc_tests, + Subcommand::Test { doc, no_doc, .. } => { + if doc { + DocTests::Only + } else if no_doc { + DocTests::No + } else { + DocTests::Yes + } + } _ => DocTests::Yes, } } @@ -850,18 +529,22 @@ impl Subcommand { } } -fn split(s: &[String]) -> Vec { - s.iter().flat_map(|s| s.split(',')).filter(|s| !s.is_empty()).map(|s| s.to_string()).collect() -} - -fn parse_deny_warnings(matches: &getopts::Matches) -> Option { - match matches.opt_str("warnings").as_deref() { - Some("deny") => Some(true), - Some("warn") => Some(false), - Some(value) => { - eprintln!(r#"invalid value for --warnings: {:?}, expected "warn" or "deny""#, value,); - crate::detail_exit(1); - } - None => None, +/// Returns the shell completion for a given shell, if the result differs from the current +/// content of `path`. If `path` does not exist, always returns `Some`. +pub fn get_completion(shell: G, path: &Path) -> Option { + let mut cmd = Flags::command(); + let current = if !path.exists() { + String::new() + } else { + std::fs::read_to_string(path).unwrap_or_else(|_| { + eprintln!("couldn't read {}", path.display()); + crate::detail_exit(1) + }) + }; + let mut buf = Vec::new(); + clap_complete::generate(shell, &mut cmd, "x.py", &mut buf); + if buf == current.as_bytes() { + return None; } + Some(String::from_utf8(buf).expect("completion script should be UTF-8")) } -- cgit v1.2.3