//! Command-line interface of the rustbuild build system. //! //! This module implements the command-line parsing of the build system which //! has various flags to configure how it's run. use std::path::{Path, PathBuf}; use clap::{CommandFactory, Parser, ValueEnum}; use crate::builder::{Builder, Kind}; use crate::config::{target_selection_list, Config, TargetSelectionList}; use crate::setup::Profile; use crate::{Build, DocTests}; #[derive(Copy, Clone, Default, Debug, ValueEnum)] pub enum Color { Always, Never, #[default] Auto, } /// 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 { #[command(subcommand)] pub cmd: Subcommand, #[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, #[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, // keeping for client backward compatibility #[arg(global(true), long, value_name = "PATH")] /// build paths to skip pub skip: 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, #[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, #[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. #[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, #[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, /// Additional reproducible artifacts that should be added to the reproducible artifacts archive. #[arg(global(true), long)] pub reproducible_artifact: Vec, #[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::exit!(0); } Flags::parse_from(it) } } #[derive(Debug, Clone, Default, clap::Subcommand)] pub enum Subcommand { #[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 { #[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, /// 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 { /// 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 { #[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 next-solver 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 { #[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)] /// comma-separated list of other files types to check (accepts py, py:lint, /// py:fmt, shell) extra_checks: Option, #[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, #[arg(long)] /// enable this to generate a Rustfix coverage file, which is saved in /// `//rustfix_missing_coverage.txt` rustfix_coverage: bool, }, /// Build and run some benchmarks Bench { #[arg(long, allow_hyphen_values(true))] test_args: Vec, }, /// Clean out build directories Clean { #[arg(long)] /// Clean the entire build directory (not used by default) all: bool, #[arg(long, value_name = "N")] /// Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used. stage: Option, }, /// 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 { /// arguments for the tool #[arg(long, allow_hyphen_values(true))] args: Vec, }, /// 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. 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 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()))] 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 { pub fn kind(&self) -> Kind { match self { Subcommand::Bench { .. } => Kind::Bench, Subcommand::Build { .. } => Kind::Build, Subcommand::Check { .. } => Kind::Check, Subcommand::Clippy { .. } => Kind::Clippy, Subcommand::Doc { .. } => Kind::Doc, Subcommand::Fix { .. } => Kind::Fix, Subcommand::Format { .. } => Kind::Format, Subcommand::Test { .. } => Kind::Test, Subcommand::Clean { .. } => Kind::Clean, Subcommand::Dist { .. } => Kind::Dist, Subcommand::Install { .. } => Kind::Install, Subcommand::Run { .. } => Kind::Run, Subcommand::Setup { .. } => Kind::Setup, Subcommand::Suggest { .. } => Kind::Suggest, } } pub fn rustc_args(&self) -> Vec<&str> { match *self { Subcommand::Test { ref rustc_args, .. } => { rustc_args.iter().flat_map(|s| s.split_whitespace()).collect() } _ => vec![], } } pub fn fail_fast(&self) -> bool { match *self { Subcommand::Test { no_fail_fast, .. } => !no_fail_fast, _ => false, } } pub fn doc_tests(&self) -> DocTests { match *self { Subcommand::Test { doc, no_doc, .. } => { if doc { DocTests::Only } else if no_doc { DocTests::No } else { DocTests::Yes } } _ => DocTests::Yes, } } pub fn bless(&self) -> bool { match *self { Subcommand::Test { bless, .. } => bless, _ => false, } } pub fn extra_checks(&self) -> Option<&str> { match *self { Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str), _ => None, } } pub fn only_modified(&self) -> bool { match *self { Subcommand::Test { only_modified, .. } => only_modified, _ => false, } } pub fn force_rerun(&self) -> bool { match *self { Subcommand::Test { force_rerun, .. } => force_rerun, _ => false, } } pub fn rustfix_coverage(&self) -> bool { match *self { Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage, _ => false, } } pub fn compare_mode(&self) -> Option<&str> { match *self { Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]), _ => None, } } pub fn pass(&self) -> Option<&str> { match *self { Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]), _ => None, } } pub fn run(&self) -> Option<&str> { match *self { Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]), _ => None, } } pub fn open(&self) -> bool { match *self { Subcommand::Doc { open, .. } => open, _ => false, } } pub fn json(&self) -> bool { match *self { Subcommand::Doc { json, .. } => json, _ => false, } } } /// 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::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")) }