use bpaf::{construct, long, short, Bpaf, Parser};
use cargo_metadata::Artifact;
use std::path::PathBuf;
fn check_target_dir(path: PathBuf) -> anyhow::Result<PathBuf> {
if path.is_dir() {
Ok(path)
} else {
std::fs::create_dir(&path)?;
Ok(std::fs::canonicalize(path)?)
}
}
#[derive(Clone, Debug, Bpaf)]
#[bpaf(options("asm"), version)]
#[allow(clippy::struct_excessive_bools)]
#[allow(clippy::doc_markdown)]
pub struct Options {
#[bpaf(long, short, argument("SPEC"))]
pub package: Option<String>,
#[bpaf(external, optional)]
pub focus: Option<Focus>,
#[bpaf(external)]
pub cargo: Cargo,
#[bpaf(short('M'), long)]
pub mca_arg: Vec<String>,
#[bpaf(external)]
pub target_cpu: Option<String>,
#[bpaf(external)]
pub format: Format,
#[bpaf(external)]
pub syntax: Syntax,
#[bpaf(external)]
pub to_dump: ToDump,
}
#[derive(Debug, Clone, Bpaf)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cargo {
#[bpaf(external, hide_usage)]
pub manifest_path: PathBuf,
#[bpaf(
env("CARGO_TARGET_DIR"),
argument("DIR"),
parse(check_target_dir),
optional,
hide_usage
)]
pub target_dir: Option<PathBuf>,
#[bpaf(hide_usage)]
pub dry: bool,
#[bpaf(hide_usage)]
pub frozen: bool,
#[bpaf(hide_usage)]
pub locked: bool,
#[bpaf(hide_usage)]
pub offline: bool,
#[bpaf(external, hide_usage)]
pub cli_features: CliFeatures,
#[bpaf(external)]
pub compile_mode: CompileMode,
#[bpaf(argument("TRIPLE"))]
pub target: Option<String>,
#[bpaf(short('Z'), argument("FLAG"))]
pub unstable: Vec<String>,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(fallback(ToDump::Unspecified))]
pub enum ToDump {
Everything,
ByIndex {
#[bpaf(positional("ITEM_INDEX"))]
value: usize,
},
Function {
#[bpaf(positional("FUNCTION"))]
function: String,
#[bpaf(positional("INDEX"))]
nth: Option<usize>,
},
#[bpaf(hide)]
Unspecified,
}
fn target_cpu() -> impl Parser<Option<String>> {
let native = long("native")
.help("Optimize for the CPU running the compiler")
.req_flag("native".to_string());
let cpu = long("target-cpu")
.help("Optimize code for a specific CPU, see 'rustc --print target-cpus'")
.argument::<String>("CPU");
construct!([native, cpu]).optional()
}
#[derive(Bpaf, Clone, Debug)]
pub struct CliFeatures {
pub no_default_features: bool,
pub all_features: bool,
#[bpaf(argument("FEATURE"))]
pub features: Vec<String>,
}
#[derive(Bpaf, Clone, Debug)]
#[bpaf(fallback(CompileMode::Release))]
pub enum CompileMode {
Release,
Dev,
Custom(
#[bpaf(long("profile"), argument("PROFILE"))]
String,
),
}
fn verbosity() -> impl Parser<usize> {
short('v')
.long("verbose")
.help("more verbose output, can be specified multiple times")
.req_flag(())
.many()
.map(|v| v.len())
.hide_usage()
}
fn manifest_path() -> impl Parser<PathBuf> {
long("manifest-path")
.help("Path to Cargo.toml")
.argument::<PathBuf>("PATH")
.parse(|p| {
if p.is_absolute() {
Ok(p)
} else {
std::env::current_dir()
.map(|d| d.join(p))
.and_then(|full_path| full_path.canonicalize())
}
})
.fallback_with(|| std::env::current_dir().map(|x| x.join("Cargo.toml")))
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Bpaf, Copy)]
pub struct Format {
pub rust: bool,
#[bpaf(external(color_detection), hide_usage)]
pub color: bool,
#[bpaf(hide_usage)]
pub full_name: bool,
#[bpaf(hide_usage)]
pub keep_labels: bool,
#[bpaf(external)]
pub verbosity: usize,
pub simplify: bool,
}
#[derive(Debug, Clone, Bpaf, Eq, PartialEq, Copy)]
#[bpaf(fallback(Syntax::Intel))]
pub enum Syntax {
#[bpaf(long("intel"), long("asm"))]
Intel,
Att,
Llvm,
Mir,
Wasm,
#[bpaf(long("mca-intel"), long("mca"))]
McaIntel,
McaAtt,
}
impl Syntax {
#[must_use]
pub fn format(&self) -> Option<&str> {
match self {
Self::Intel | Self::McaIntel => Some("llvm-args=-x86-asm-syntax=intel"),
Self::Att | Self::McaAtt => Some("llvm-args=-x86-asm-syntax=att"),
Self::Wasm | Self::Mir | Self::Llvm => None,
}
}
#[must_use]
pub fn emit(&self) -> &str {
match self {
Self::Intel | Self::Att | Self::Wasm | Self::McaIntel | Self::McaAtt => "asm",
Self::Llvm => "llvm-ir",
Self::Mir => "mir",
}
}
#[must_use]
pub fn ext(&self) -> &str {
match self {
Self::Intel | Self::McaAtt | Self::McaIntel | Self::Att | Self::Wasm => "s",
Self::Llvm => "ll",
Self::Mir => "mir",
}
}
}
fn color_detection() -> impl Parser<bool> {
let yes = long("color")
.help("Enable color highlighting")
.req_flag(true);
let no = long("no-color")
.help("Disable color highlighting")
.req_flag(false);
construct!([yes, no]).fallback_with::<_, &str>(|| {
Ok(supports_color::on(supports_color::Stream::Stdout).is_some())
})
}
#[derive(Debug, Clone, Bpaf)]
pub enum Focus {
Lib,
Test(
#[bpaf(long("test"), argument("TEST"))]
String,
),
#[bpaf(long("test"), hide)]
TestList,
Bench(
#[bpaf(long("bench"), argument("BENCH"))]
String,
),
#[bpaf(long("bench"), hide)]
BenchList,
Example(
#[bpaf(long("example"), argument("EXAMPLE"))]
String,
),
#[bpaf(long("example"), hide)]
ExampleList,
Bin(
#[bpaf(long("bin"), argument("BIN"))]
String,
),
#[bpaf(long("bin"), hide)]
BinList,
}
impl TryFrom<&'_ cargo_metadata::Target> for Focus {
type Error = anyhow::Error;
fn try_from(target: &cargo_metadata::Target) -> Result<Self, Self::Error> {
match target.kind.first().map(|s| &**s) {
Some("lib" | "rlib") => Ok(Focus::Lib),
Some("test") => Ok(Focus::Test(target.name.clone())),
Some("bench") => Ok(Focus::Bench(target.name.clone())),
Some("example") => Ok(Focus::Example(target.name.clone())),
Some("bin") => Ok(Focus::Bin(target.name.clone())),
_ => anyhow::bail!("Unknow target kind: {:?}", target.kind),
}
}
}
impl Focus {
#[must_use]
pub fn as_parts(&self) -> (&str, Option<&str>) {
match self {
Focus::Lib => ("lib", None),
Focus::Test(name) => ("test", Some(name)),
Focus::TestList => ("test", None),
Focus::Bench(name) => ("bench", Some(name)),
Focus::BenchList => ("bench", None),
Focus::Example(name) => ("example", Some(name)),
Focus::ExampleList => ("example", None),
Focus::Bin(name) => ("bin", Some(name)),
Focus::BinList => ("bin", None),
}
}
pub fn as_cargo_args(&self) -> impl Iterator<Item = String> {
let (kind, name) = self.as_parts();
std::iter::once(format!("--{kind}")).chain(name.map(ToOwned::to_owned))
}
#[must_use]
pub fn matches_artifact(&self, artifact: &Artifact) -> bool {
let (kind, name) = self.as_parts();
let somewhat_matches = kind == "lib" && artifact.target.kind.iter().any(|i| i == "rlib");
let kind_matches = artifact.target.kind == [kind];
(somewhat_matches || kind_matches)
&& name.map_or(true, |name| artifact.target.name == *name)
}
}