use anyhow::Result;
use cargo::core::resolver::CliFeatures;
use cargo::ops::Packages;
use std::fmt;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
pub(crate) enum Cli {
#[structopt(
name = "instruments",
after_help = "EXAMPLE:\n cargo instruments -t time Profile main binary with the (recommended) Time Profiler."
)]
Instruments(AppConfig),
}
#[derive(Debug, StructOpt)]
#[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)]
pub(crate) struct AppConfig {
#[structopt(short = "l", long)]
pub(crate) list_templates: bool,
#[structopt(
short = "t",
long = "template",
value_name = "TEMPLATE",
required_unless = "list-templates"
)]
pub(crate) template_name: Option<String>,
#[structopt(short = "p", long, value_name = "NAME")]
package: Option<String>,
#[structopt(long, group = "target", value_name = "NAME")]
example: Option<String>,
#[structopt(long, group = "target", value_name = "NAME")]
bin: Option<String>,
#[structopt(long, group = "target", value_name = "NAME")]
bench: Option<String>,
#[structopt(long, conflicts_with = "profile")]
release: bool,
#[structopt(long, value_name = "NAME")]
profile: Option<String>,
#[structopt(short = "o", long = "output", value_name = "PATH", parse(from_os_str))]
pub(crate) trace_filepath: Option<PathBuf>,
#[structopt(long, value_name = "MILLIS")]
pub(crate) time_limit: Option<usize>,
#[structopt(long, hidden = true)]
pub(crate) open: bool,
#[structopt(long)]
pub(crate) no_open: bool,
#[structopt(long, value_name = "CARGO-FEATURES")]
pub(crate) features: Option<String>,
#[structopt(long, value_name = "PATH")]
pub(crate) manifest_path: Option<PathBuf>,
#[structopt(long, display_order = 1001)]
pub(crate) all_features: bool,
#[structopt(long, display_order = 1001)]
pub(crate) no_default_features: bool,
#[structopt(value_name = "ARGS")]
pub(crate) target_args: Vec<String>,
}
#[derive(Debug, PartialEq)]
pub(crate) enum Target {
Default,
Example(String),
Bin(String),
Bench(String),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Package {
Default,
Package(String),
}
impl From<Package> for Packages {
fn from(p: Package) -> Self {
match p {
Package::Default => Packages::Default,
Package::Package(s) => Packages::Packages(vec![s]),
}
}
}
impl fmt::Display for Package {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Package::Default => {
write!(f, "Default: search all packages for example/bin/bench")
}
Package::Package(s) => write!(f, "{}", s),
}
}
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Target::Default => write!(f, "default"),
Target::Example(bin) => write!(f, "{bin} (example)",),
Target::Bin(bin) => write!(f, "{bin} (bin)"),
Target::Bench(bench) => write!(f, "{bench} (bench)"),
}
}
}
pub(crate) struct CargoOpts {
pub(crate) package: Package,
pub(crate) target: Target,
pub(crate) profile: String,
pub(crate) features: CliFeatures,
}
impl AppConfig {
pub(crate) fn to_cargo_opts(&self) -> Result<CargoOpts> {
let package = self.get_package();
let target = self.get_target();
let features = self.features.clone().map(|s| vec![s]).unwrap_or_default();
let features = CliFeatures::from_command_line(
&features,
self.all_features,
!self.no_default_features,
)?;
let profile = self
.profile
.clone()
.unwrap_or_else(|| (if self.release { "release" } else { "dev" }).to_owned());
Ok(CargoOpts { package, target, profile, features })
}
fn get_package(&self) -> Package {
if let Some(ref package) = self.package {
Package::Package(package.clone())
} else {
Package::Default
}
}
fn get_target(&self) -> Target {
if let Some(ref example) = self.example {
Target::Example(example.clone())
} else if let Some(ref bin) = self.bin {
Target::Bin(bin.clone())
} else if let Some(ref bench) = self.bench {
Target::Bench(bench.clone())
} else {
Target::Default
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults() {
let opts = AppConfig::from_iter(&["instruments", "-t", "template"]);
assert!(opts.example.is_none());
assert!(opts.bin.is_none());
assert!(!opts.release);
assert!(opts.trace_filepath.is_none());
assert!(opts.package.is_none());
assert!(opts.manifest_path.is_none());
}
#[test]
fn package_is_given() {
let opts =
AppConfig::from_iter(&["instruments", "--package", "foo", "--template", "alloc"]);
assert!(opts.example.is_none());
assert!(opts.bin.is_none());
assert!(opts.bench.is_none());
assert_eq!(opts.package.unwrap().as_str(), "foo");
let opts = AppConfig::from_iter(&[
"instruments",
"--package",
"foo",
"--template",
"alloc",
"--bin",
"bin_arg",
]);
assert!(opts.example.is_none());
assert!(opts.bench.is_none());
assert_eq!(opts.bin.unwrap().as_str(), "bin_arg");
assert_eq!(opts.package.unwrap().as_str(), "foo");
}
#[test]
#[should_panic(expected = "cannot be used with one or more of the other")]
fn group_is_exclusive() {
let opts = AppConfig::from_iter(&["instruments", "-t", "time", "--bin", "bin_arg"]);
assert!(opts.example.is_none());
assert_eq!(opts.bin.unwrap().as_str(), "bin_arg");
let opts =
AppConfig::from_iter(&["instruments", "-t", "time", "--example", "example_binary"]);
assert!(opts.bin.is_none());
assert_eq!(opts.example.unwrap().as_str(), "example_binary");
let _opts = AppConfig::from_iter_safe(&[
"instruments",
"-t",
"time",
"--bin",
"thing",
"--example",
"other",
])
.unwrap();
}
#[test]
fn limit_millis() {
let opts = AppConfig::from_iter(&["instruments", "-t", "time", "--time-limit", "42000"]);
assert_eq!(opts.time_limit, Some(42000));
let opts = AppConfig::from_iter(&["instruments", "-t", "time", "--time-limit", "808"]);
assert_eq!(opts.time_limit, Some(808));
let opts = AppConfig::from_iter(&["instruments", "-t", "time"]);
assert_eq!(opts.time_limit, None);
}
#[test]
fn features() {
let opts = &[
"instruments",
"--template",
"time",
"--example",
"hello",
"--features",
"svg im",
"--",
"hi",
];
let opts = AppConfig::from_iter(opts);
assert_eq!(opts.template_name, Some("time".into()));
assert_eq!(opts.example, Some("hello".to_string()));
assert_eq!(opts.features, Some("svg im".to_string()));
let features: Vec<_> = opts
.to_cargo_opts()
.unwrap()
.features
.features
.iter()
.map(|feat| feat.to_string())
.collect();
assert_eq!(features, vec!["im", "svg"]);
}
#[test]
fn var_args() {
let opts = AppConfig::from_iter(&[
"instruments",
"-t",
"alloc",
"--time-limit",
"808",
"--",
"hi",
"-h",
"--bin",
]);
assert_eq!(opts.template_name, Some("alloc".into()));
assert_eq!(opts.time_limit, Some(808));
assert_eq!(opts.target_args, vec!["hi", "-h", "--bin"]);
}
#[test]
fn manifest_path() {
let opts = AppConfig::from_iter(&[
"instruments",
"--manifest-path",
"/path/to/Cargo.toml",
"--template",
"alloc",
]);
assert!(opts.example.is_none());
assert!(opts.bin.is_none());
assert!(opts.bench.is_none());
assert!(opts.package.is_none());
assert_eq!(opts.manifest_path.unwrap(), PathBuf::from("/path/to/Cargo.toml"));
}
}