use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let java_source_dir = manifest_dir.join("java");
println!("cargo:rerun-if-changed=java");
let android_sdk_root = env::var("ANDROID_SDK_ROOT")
.or_else(|_| env::var("ANDROID_HOME"))
.expect("ANDROID_SDK_ROOT or ANDROID_HOME environment variable must be set.");
let sdk_root_path = Path::new(&android_sdk_root);
let build_tools_dir = sdk_root_path.join("build-tools");
let latest_build_tools =
find_latest_sdk_dir(&build_tools_dir).expect("No build-tools version found in SDK.");
println!(
"cargo:warning=Using Build Tools version: {}",
latest_build_tools.file_name().unwrap().to_str().unwrap()
);
let d8_executable = if cfg!(target_os = "windows") {
"d8.bat"
} else {
"d8"
};
let d8_path = latest_build_tools.join(d8_executable);
let platforms_dir = sdk_root_path.join("platforms");
let latest_platform =
find_latest_sdk_dir(&platforms_dir).expect("No platform version found in SDK.");
println!(
"cargo:warning=Using Platform version: {}",
latest_platform.file_name().unwrap().to_str().unwrap()
);
let android_jar = latest_platform.join("android.jar");
if !android_jar.exists() {
panic!("android.jar not found in {:?}.", latest_platform);
}
let java_classes_dir = out_dir.join("java_classes");
std::fs::create_dir_all(&java_classes_dir).unwrap();
let java_files: Vec<PathBuf> = walkdir::WalkDir::new(&java_source_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.path().extension().map_or(false, |ext| ext == "java"))
.map(|e| e.into_path())
.collect();
if java_files.is_empty() {
return;
}
let javac_status = Command::new("javac")
.args(["-d", java_classes_dir.to_str().unwrap()])
.args(["-cp", android_jar.to_str().unwrap()])
.args(["-encoding", "UTF-8"])
.args(["-sourcepath", java_source_dir.to_str().unwrap()])
.args(&java_files)
.status()
.expect("Failed to execute javac.");
if !javac_status.success() {
panic!("Java compilation failed.");
}
let class_files: Vec<PathBuf> = walkdir::WalkDir::new(&java_classes_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.path().extension().map_or(false, |ext| ext == "class"))
.map(|e| e.into_path())
.collect();
if class_files.is_empty() {
return;
}
println!("class_files = {:?}", class_files);
let final_dex_path = out_dir.join("permission_manager.dex");
println!("final_dex_path = {:?}", final_dex_path);
let temp_dex_dir = out_dir.join("temp_dex");
std::fs::create_dir_all(&temp_dex_dir).unwrap();
let d8_status = Command::new(&d8_path)
.arg("--output")
.arg(&temp_dex_dir)
.arg("--lib")
.arg(&android_jar)
.arg("--min-api")
.arg("21")
.args(&class_files)
.status()
.expect("Failed to execute d8. Please ensure Android build tools are installed.");
if !d8_status.success() {
panic!("d8 dex conversion failed.");
}
let generated_dex = temp_dex_dir.join("classes.dex");
if generated_dex.exists() {
std::fs::rename(&generated_dex, &final_dex_path)
.expect("Unable to move classes.dex to final location");
println!(
"cargo:warning=DEX file successfully generated and moved to: {:?}",
final_dex_path
);
} else {
panic!("d8 executed successfully, but expected classes.dex file was not found.");
}
}
fn find_latest_sdk_dir(parent_dir: &Path) -> Option<PathBuf> {
if !parent_dir.exists() {
return None;
}
let mut latest_version: Option<(Vec<u32>, PathBuf)> = None;
for entry in std::fs::read_dir(parent_dir).ok()? {
let entry = entry.ok()?;
let path = entry.path();
if path.is_dir() {
let version_str = entry.file_name().to_string_lossy().to_string();
let version_parts: Vec<u32> = version_str
.split('.')
.map(|s| s.replace("android-", "").parse().unwrap_or(0))
.collect();
if let Some((ref latest_parts, _)) = latest_version {
if version_parts > *latest_parts {
latest_version = Some((version_parts, path));
}
} else {
latest_version = Some((version_parts, path));
}
}
}
latest_version.map(|(_, path)| path)
}