use std::path::{Path, PathBuf};
use std::ffi::{OsStr, OsString};
use std::process::{Command, ExitStatus};
use crate::env_paths::{self, PathExt};
use crate::JavaRun;
#[derive(Clone, Debug, Default)]
pub struct Dexer {
java_home: Option<PathBuf>,
android_d8_jar_path: Option<PathBuf>,
release: bool,
android_min_api: Option<u32>,
no_desugaring: bool,
android_jar_path: Option<PathBuf>,
class_paths: Vec<OsString>,
out_dir: Option<OsString>,
files: Vec<OsString>,
}
impl Dexer {
pub fn new() -> Self {
Default::default()
}
pub fn run(&self) -> std::io::Result<ExitStatus> {
self.command()?.status()
}
pub fn command(&self) -> std::io::Result<Command> {
let mut d8_run = JavaRun::new();
if let Some(java_home) = &self.java_home {
d8_run.java_home(java_home);
}
let d8_jar_path = self.android_d8_jar_path
.clone()
.and_then(PathExt::path_if_exists)
.or_else(|| env_paths::android_d8_jar(None))
.ok_or_else(|| std::io::Error::other(
"d8.jar not provided, and could not be auto-discovered."
))?;
d8_run.class_path(d8_jar_path)
.main_class("com.android.tools.r8.D8");
if self.release {
d8_run.arg("--release");
}
if let Some(min_api) = self.android_min_api {
d8_run.arg("--min-api").arg(min_api.to_string());
}
if self.no_desugaring {
d8_run.arg("--no-desugaring");
}
let android_jar_path = self.android_jar_path
.clone()
.and_then(PathExt::path_if_exists)
.or_else(|| env_paths::android_jar(None))
.ok_or_else(|| std::io::Error::other(
"android.jar not provided, and could not be auto-discovered."
))?;
d8_run.arg("--lib").arg(android_jar_path);
for class_path in &self.class_paths {
d8_run.arg("--classpath").arg(class_path);
}
if let Some(out_dir) = &self.out_dir {
d8_run.arg("--output").arg(out_dir);
}
for file in &self.files {
d8_run.arg(file);
}
d8_run.command()
}
pub fn java_home<P: AsRef<OsStr>>(&mut self, java_home: P) -> &mut Self {
self.java_home = Some(java_home.as_ref().into());
self
}
pub fn android_d8_jar<P: AsRef<OsStr>>(&mut self, android_d8_jar_path: P) -> &mut Self {
self.android_d8_jar_path.replace(android_d8_jar_path.as_ref().into());
self
}
pub fn release(&mut self, release: bool) -> &mut Self {
self.release = release;
self
}
pub fn android_min_api(&mut self, api_level: u32) -> &mut Self {
self.android_min_api.replace(api_level);
self
}
pub fn no_desugaring(&mut self, no_desugaring: bool) -> &mut Self {
self.no_desugaring = no_desugaring;
self
}
pub fn android_jar<P: AsRef<OsStr>>(&mut self, android_jar_path: P) -> &mut Self {
self.android_jar_path.replace(android_jar_path.as_ref().into());
self
}
pub fn class_path<S: AsRef<OsStr>>(&mut self, class_path: S) -> &mut Self {
self.class_paths.push(class_path.as_ref().into());
self
}
pub fn out_dir<P: AsRef<OsStr>>(&mut self, out_dir: P) -> &mut Self {
self.out_dir = Some(out_dir.as_ref().into());
self
}
pub fn file<P: AsRef<OsStr>>(&mut self, file: P) -> &mut Self {
self.files.push(file.as_ref().into());
self
}
pub fn files<P>(&mut self, files: P) -> &mut Self
where
P: IntoIterator,
P::Item: AsRef<OsStr>,
{
self.files.extend(files.into_iter().map(|f| f.as_ref().into()));
self
}
pub fn collect_classes<P: AsRef<OsStr>>(&mut self, class_path: P) -> std::io::Result<&mut Self> {
let class_path = PathBuf::from(class_path.as_ref());
if !class_path.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"`class_path` is not a directory"
));
}
let extension = Some(std::ffi::OsStr::new("class"));
visit_dirs(class_path, &mut |entry| {
if entry.path().extension() == extension {
self.file(entry.path());
}
})?;
Ok(self)
}
}
fn visit_dirs(
dir: impl AsRef<Path>,
cb: &mut impl FnMut(&std::fs::DirEntry),
) -> std::io::Result<()> {
if dir.as_ref().is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, cb)?;
} else {
cb(&entry);
}
}
}
Ok(())
}