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,
}
#[derive(Copy, Clone, Default, Debug, ValueEnum)]
pub enum Warnings {
Deny,
Warn,
#[default]
Default,
}
#[derive(Debug, Parser)]
#[clap(
override_usage = "x.py <subcommand> [options] [<paths>...]",
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)]
pub verbose: u8, #[arg(global(true), short, long)]
pub incremental: bool,
#[arg(global(true), long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
pub build_dir: Option<PathBuf>,
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
pub build: Option<String>,
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
pub host: Option<TargetSelectionList>,
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
pub target: Option<TargetSelectionList>,
#[arg(global(true), long, value_name = "PATH")]
pub exclude: Vec<PathBuf>, #[arg(global(true), long, value_name = "PATH")]
pub skip: Vec<PathBuf>,
#[arg(global(true), long)]
pub include_default_paths: bool,
#[arg(global(true), value_hint = clap::ValueHint::Other, long)]
pub rustc_error_format: Option<String>,
#[arg(global(true), long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
pub on_fail: Option<String>,
#[arg(global(true), long)]
pub dry_run: bool,
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
pub stage: Option<u32>,
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
pub keep_stage: Vec<u32>,
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
pub keep_stage_std: Vec<u32>,
#[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
pub src: Option<PathBuf>,
#[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"
)]
pub jobs: usize,
#[arg(global(true), long)]
#[clap(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
pub warnings: Warnings,
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
pub error_format: Option<String>,
#[arg(global(true), long)]
pub json_output: bool,
#[arg(global(true), long, value_name = "STYLE")]
#[clap(value_enum, default_value_t = Color::Auto)]
pub color: Color,
#[arg(global(true), long, value_name = "VALUE")]
pub llvm_skip_rebuild: Option<bool>,
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub rust_profile_generate: Option<String>,
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub rust_profile_use: Option<String>,
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub llvm_profile_use: Option<String>,
#[arg(global(true), long)]
pub llvm_profile_generate: bool,
#[arg(global(true), long)]
pub reproducible_artifact: Vec<String>,
#[arg(global(true))]
pub paths: Vec<PathBuf>,
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
pub set: Vec<String>,
#[arg(global(true), last(true), value_name = "ARGS")]
pub free_args: Vec<String>,
}
impl Flags {
pub fn parse(args: &[String]) -> Self {
let first = String::from("x.py");
let it = std::iter::once(&first).chain(args.iter());
#[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 ")]
#[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.")]
Check {
#[arg(long)]
all_targets: bool,
},
#[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,
#[arg(global(true), short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
allow: Vec<String>,
#[arg(global(true), short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
deny: Vec<String>,
#[arg(global(true), short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
warn: Vec<String>,
#[arg(global(true), short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
forbid: Vec<String>,
},
#[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"
)]
Format {
#[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")]
Doc {
#[arg(long)]
open: bool,
#[arg(long)]
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")]
Test {
#[arg(long)]
no_fail_fast: bool,
#[arg(long, value_name = "SUBSTRING")]
skip: Vec<PathBuf>,
#[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
test_args: Vec<String>,
#[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
rustc_args: Vec<String>,
#[arg(long)]
no_doc: bool,
#[arg(long)]
doc: bool,
#[arg(long)]
bless: bool,
#[arg(long)]
extra_checks: Option<String>,
#[arg(long)]
force_rerun: bool,
#[arg(long)]
only_modified: bool,
#[arg(long, value_name = "COMPARE MODE")]
compare_mode: Option<String>,
#[arg(long, value_name = "check | build | run")]
pass: Option<String>,
#[arg(long, value_name = "auto | always | never")]
run: Option<String>,
#[arg(long)]
rustfix_coverage: bool,
},
Bench {
#[arg(long, allow_hyphen_values(true))]
test_args: Vec<String>,
},
Clean {
#[arg(long)]
all: bool,
#[arg(long, value_name = "N")]
stage: Option<u32>,
},
Dist,
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 {
#[arg(long, allow_hyphen_values(true))]
args: Vec<String>,
},
#[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 {
#[arg(value_name = "<PROFILE>|hook|vscode|link")]
profile: Option<PathBuf>,
},
#[clap(long_about = "\n")]
Suggest {
#[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,
}
}
}
pub fn get_completion<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
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"))
}