use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=java");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os != "android" {
return;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let java_source_dir = manifest_dir.join("java");
if !java_source_dir.exists() {
println!("cargo:warning=No java directory found, skipping Java compilation");
return;
}
let android_sdk_root = match env::var("ANDROID_SDK_ROOT").or_else(|_| env::var("ANDROID_HOME"))
{
Ok(path) => path,
Err(_) => {
println!("cargo:warning=ANDROID_SDK_ROOT or ANDROID_HOME environment variable not set, skipping Java compilation");
return;
}
};
let sdk_root_path = Path::new(&android_sdk_root);
if !sdk_root_path.exists() {
println!(
"cargo:warning=Android SDK path does not exist: {:?}, skipping Java compilation",
sdk_root_path
);
return;
}
let build_tools_dir = sdk_root_path.join("build-tools");
let latest_build_tools = match find_latest_sdk_dir(&build_tools_dir) {
Some(path) => path,
None => {
println!(
"cargo:warning=No build-tools version found in SDK, skipping Java compilation"
);
return;
}
};
let d8_executable = if cfg!(target_os = "windows") {
"d8.bat"
} else {
"d8"
};
let d8_path = latest_build_tools.join(d8_executable);
if !d8_path.exists() {
println!(
"cargo:warning=d8 not found at {:?}, skipping Java compilation",
d8_path
);
return;
}
let platforms_dir = sdk_root_path.join("platforms");
let latest_platform = match find_latest_sdk_dir(&platforms_dir) {
Some(path) => path,
None => {
println!("cargo:warning=No platform version found in SDK, skipping Java compilation");
return;
}
};
let android_jar = latest_platform.join("android.jar");
if !android_jar.exists() {
println!(
"cargo:warning=android.jar not found in {:?}, skipping Java compilation",
latest_platform
);
return;
}
let java_classes_dir = out_dir.join("java_classes");
if let Err(e) = fs::create_dir_all(&java_classes_dir) {
println!(
"cargo:warning=Failed to create java_classes directory: {}",
e
);
return;
}
let java_files = find_java_files(&java_source_dir);
if java_files.is_empty() {
println!("cargo:warning=No Java files found in java directory");
return;
}
let javac_path = match find_javac() {
Some(path) => path,
None => {
println!("cargo:warning=Unable to find javac compiler. Please ensure it is installed and either JAVA_HOME is set or javac is in PATH");
return;
}
};
let javac_status = Command::new(&javac_path)
.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(["-source", "8"])
.args(["-target", "8"])
.args(&java_files)
.status();
match javac_status {
Ok(status) => {
if !status.success() {
println!("cargo:warning=Java compilation failed");
return;
}
}
Err(e) => {
println!(
"cargo:warning=Failed to execute javac at {:?}: {}",
javac_path, e
);
return;
}
}
let class_files = find_class_files(&java_classes_dir);
if class_files.is_empty() {
println!("cargo:warning=No class files found for DEX conversion");
return;
}
let final_dex_path = out_dir.join("presentation_helper.dex");
let temp_dex_dir = out_dir.join("temp_dex");
if let Err(e) = fs::create_dir_all(&temp_dex_dir) {
println!("cargo:warning=Failed to create temp_dex directory: {}", e);
return;
}
let dx_executable = if cfg!(target_os = "windows") {
"dx.bat"
} else {
"dx"
};
let dx_path = latest_build_tools.join(dx_executable);
let dex_command_result = if dx_path.exists() {
Command::new(&dx_path)
.arg("--dex")
.arg("--output")
.arg(&final_dex_path)
.args(&class_files)
.status()
} else {
Command::new(&d8_path)
.arg("--output")
.arg(&temp_dex_dir)
.arg("--classpath")
.arg(&android_jar)
.arg("--min-api")
.arg("21")
.arg("--no-desugaring")
.args(&class_files)
.status()
};
let d8_status = dex_command_result;
match d8_status {
Ok(status) => {
if !status.success() {
println!("cargo:warning=DEX conversion failed");
return;
}
}
Err(e) => {
println!("cargo:warning=Failed to execute DEX tool: {}", e);
return;
}
}
let dex_exists = if final_dex_path.exists() {
true
} else {
let generated_dex = temp_dex_dir.join("classes.dex");
if generated_dex.exists() {
if let Err(e) = fs::rename(&generated_dex, &final_dex_path) {
println!(
"cargo:warning=Unable to move classes.dex to final location: {}",
e
);
return;
}
true
} else {
false
}
};
if dex_exists {
match fs::read(&final_dex_path) {
Ok(dex_data) => {
if dex_data.len() < 8 {
println!(
"cargo:warning=Generated DEX file is too small: {} bytes",
dex_data.len()
);
return;
}
let dex_magic = &dex_data[0..4];
if dex_magic != b"dex\n" {
println!("cargo:warning=Generated file is not a valid DEX file (incorrect magic number)");
return;
}
}
Err(e) => {
println!("cargo:warning=Failed to validate DEX file: {}", e);
return;
}
}
println!(
"cargo:rustc-env=PRESENTATION_DEX_PATH={}",
final_dex_path.display()
);
} else {
println!(
"cargo:warning=DEX tool executed successfully, but expected 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 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)
}
fn find_javac() -> Option<PathBuf> {
if let Ok(java_home) = env::var("JAVA_HOME") {
let java_home_path = Path::new(&java_home);
let javac_paths = [
java_home_path.join("bin").join("javac.exe"),
java_home_path.join("bin").join("javac"),
];
for javac_path in &javac_paths {
if javac_path.exists() {
return Some(javac_path.clone());
}
}
}
Some(PathBuf::from("javac"))
}
fn find_java_files(dir: &Path) -> Vec<PathBuf> {
let mut java_files = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
java_files.extend(find_java_files(&path));
} else if path.extension().and_then(|s| s.to_str()) == Some("java") {
java_files.push(path);
}
}
}
java_files
}
fn find_class_files(dir: &Path) -> Vec<PathBuf> {
let mut class_files = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
class_files.extend(find_class_files(&path));
} else if path.extension().and_then(|s| s.to_str()) == Some("class") {
class_files.push(path);
}
}
}
class_files
}