use std::collections::HashMap;
use cargo_apk::{ApkBuilder, Error};
use cargo_subcommand::Subcommand;
use clap::{CommandFactory, FromArgMatches, Parser};
#[derive(Parser)]
struct Cmd {
#[clap(subcommand)]
apk: ApkCmd,
}
#[derive(clap::Subcommand)]
enum ApkCmd {
Apk {
#[clap(subcommand)]
cmd: ApkSubCmd,
},
}
#[derive(Clone, Debug, Eq, PartialEq, Parser)]
#[group(skip)]
struct Args {
#[clap(flatten)]
subcommand_args: cargo_subcommand::Args,
#[clap(short, long)]
device: Option<String>,
}
#[derive(clap::Subcommand)]
enum ApkSubCmd {
#[clap(visible_alias = "c")]
Check {
#[clap(flatten)]
args: Args,
},
#[clap(visible_alias = "b")]
Build {
#[clap(flatten)]
args: Args,
},
#[clap(name = "--")]
Ndk {
cargo_cmd: String,
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
cargo_args: Vec<String>,
},
#[clap(visible_alias = "r")]
Run {
#[clap(flatten)]
args: Args,
#[clap(short, long)]
no_logcat: bool,
},
Gdb {
#[clap(flatten)]
args: Args,
},
Version,
}
fn split_apk_and_cargo_args(input: Vec<String>) -> (Args, Vec<String>) {
let known_args_taking_value = Args::command()
.get_arguments()
.flat_map(|arg| {
assert!(!arg.is_positional());
arg.get_short_and_visible_aliases()
.iter()
.flat_map(|shorts| shorts.iter().map(|short| format!("-{}", short)))
.chain(
arg.get_long_and_visible_aliases()
.iter()
.flat_map(|longs| longs.iter().map(|short| format!("--{}", short))),
)
.map(|arg_str| (arg_str, arg.get_action().takes_values()))
.collect::<Vec<_>>()
})
.collect::<HashMap<_, _>>();
#[derive(Debug, Default)]
struct SplitArgs {
apk_args: Vec<String>,
cargo_args: Vec<String>,
next_takes_value: bool,
}
let split_args = input
.into_iter()
.fold(SplitArgs::default(), |mut split_args, elem| {
let known_arg = known_args_taking_value.get(&elem);
if known_arg.is_some() || split_args.next_takes_value {
split_args.apk_args.push(elem)
} else {
split_args.cargo_args.push(elem)
}
split_args.next_takes_value = known_arg.copied().unwrap_or(false);
split_args
});
let m = Args::command()
.no_binary_name(true)
.get_matches_from(&split_args.apk_args);
let args = Args::from_arg_matches(&m).unwrap();
(args, split_args.cargo_args)
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let Cmd {
apk: ApkCmd::Apk { cmd },
} = Cmd::parse();
match cmd {
ApkSubCmd::Check { args } => {
let cmd = Subcommand::new(args.subcommand_args)?;
let builder = ApkBuilder::from_subcommand(&cmd, args.device)?;
builder.check()?;
}
ApkSubCmd::Build { args } => {
let cmd = Subcommand::new(args.subcommand_args)?;
let builder = ApkBuilder::from_subcommand(&cmd, args.device)?;
for artifact in cmd.artifacts() {
builder.build(artifact)?;
}
}
ApkSubCmd::Ndk {
cargo_cmd,
cargo_args,
} => {
let (args, cargo_args) = split_apk_and_cargo_args(cargo_args);
let cmd = Subcommand::new(args.subcommand_args)?;
let builder = ApkBuilder::from_subcommand(&cmd, args.device)?;
builder.default(&cargo_cmd, &cargo_args)?;
}
ApkSubCmd::Run { args, no_logcat } => {
let cmd = Subcommand::new(args.subcommand_args)?;
let builder = ApkBuilder::from_subcommand(&cmd, args.device)?;
anyhow::ensure!(cmd.artifacts().len() == 1, Error::invalid_args());
builder.run(&cmd.artifacts()[0], no_logcat)?;
}
ApkSubCmd::Gdb { args } => {
let cmd = Subcommand::new(args.subcommand_args)?;
let builder = ApkBuilder::from_subcommand(&cmd, args.device)?;
anyhow::ensure!(cmd.artifacts().len() == 1, Error::invalid_args());
builder.gdb(&cmd.artifacts()[0])?;
}
ApkSubCmd::Version => {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}
}
Ok(())
}
#[test]
fn test_split_apk_and_cargo_args() {
let args_default = Args::parse_from(std::iter::empty::<&str>());
assert_eq!(
split_apk_and_cargo_args(vec!["--quiet".to_string()]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
..args_default.subcommand_args.clone()
},
..args_default.clone()
},
vec![]
)
);
assert_eq!(
split_apk_and_cargo_args(vec!["unrecognized".to_string(), "--quiet".to_string()]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
..args_default.subcommand_args.clone()
},
..args_default.clone()
},
vec!["unrecognized".to_string()]
)
);
assert_eq!(
split_apk_and_cargo_args(vec!["--unrecognized".to_string(), "--quiet".to_string()]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
..args_default.subcommand_args.clone()
},
..args_default.clone()
},
vec!["--unrecognized".to_string()]
)
);
assert_eq!(
split_apk_and_cargo_args(vec!["-p".to_string(), "foo".to_string()]),
(
Args {
subcommand_args: cargo_subcommand::Args {
package: vec!["foo".to_string()],
..args_default.subcommand_args.clone()
},
..args_default.clone()
},
vec![]
)
);
assert_eq!(
split_apk_and_cargo_args(vec![
"-p".to_string(),
"foo".to_string(),
"--unrecognized".to_string(),
"--quiet".to_string()
]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
package: vec!["foo".to_string()],
..args_default.subcommand_args.clone()
},
..args_default.clone()
},
vec!["--unrecognized".to_string()]
)
);
assert_eq!(
split_apk_and_cargo_args(vec![
"--no-deps".to_string(),
"-p".to_string(),
"foo".to_string(),
"--unrecognized".to_string(),
"--quiet".to_string()
]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
package: vec!["foo".to_string()],
..args_default.subcommand_args.clone()
},
..args_default
},
vec!["--no-deps".to_string(), "--unrecognized".to_string()]
)
);
assert_eq!(
split_apk_and_cargo_args(vec![
"--no-deps".to_string(),
"--device".to_string(),
"adb:test".to_string(),
"--unrecognized".to_string(),
"--quiet".to_string()
]),
(
Args {
subcommand_args: cargo_subcommand::Args {
quiet: true,
..args_default.subcommand_args
},
device: Some("adb:test".to_string()),
},
vec!["--no-deps".to_string(), "--unrecognized".to_string()]
)
);
}