whisker_dev_server/hotpatch/
android_ndk.rs1use anyhow::Result;
22use std::path::{Path, PathBuf};
23
24const PREFERRED_NDKS: &[&str] = &[
27 "23.1.7779620",
28 "25.1.8937393",
29 "26.1.10909125",
30 "26.3.11579264",
31 "27.0.12077973",
32 "27.1.12297006",
33];
34
35pub fn android_home() -> Result<PathBuf> {
38 if let Some(p) = std::env::var_os("ANDROID_HOME").map(PathBuf::from) {
39 if p.is_dir() {
40 return Ok(p);
41 }
42 }
43 if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
44 let cand = home.join("Library/Android/sdk");
45 if cand.is_dir() {
46 return Ok(cand);
47 }
48 }
49 anyhow::bail!(
50 "ANDROID_HOME not set and no SDK found at the default macOS \
51 location ($HOME/Library/Android/sdk)."
52 )
53}
54
55pub fn ndk_home() -> Result<PathBuf> {
59 if let Some(p) = std::env::var_os("ANDROID_NDK_HOME").map(PathBuf::from) {
60 if p.is_dir() {
61 return Ok(p);
62 }
63 }
64 let ndk_dir = android_home()?.join("ndk");
65 for version in PREFERRED_NDKS {
66 let cand = ndk_dir.join(version);
67 if cand.is_dir() {
68 return Ok(cand);
69 }
70 }
71 anyhow::bail!(
72 "no supported NDK found in {} (need one of: {})",
73 ndk_dir.display(),
74 PREFERRED_NDKS.join(", "),
75 )
76}
77
78pub fn host_tag() -> Result<&'static str> {
81 if cfg!(target_os = "macos") {
82 Ok("darwin-x86_64")
83 } else if cfg!(target_os = "linux") {
84 Ok("linux-x86_64")
85 } else if cfg!(target_os = "windows") {
86 Ok("windows-x86_64")
87 } else {
88 anyhow::bail!("unsupported host OS for Android cross-compilation")
89 }
90}
91
92pub fn clang_target_prefix(abi: &str) -> Result<&'static str> {
96 match abi {
97 "arm64-v8a" => Ok("aarch64-linux-android"),
98 "armeabi-v7a" => Ok("armv7a-linux-androideabi"),
99 "x86_64" => Ok("x86_64-linux-android"),
100 "x86" => Ok("i686-linux-android"),
101 other => anyhow::bail!("unknown Android ABI: {other}"),
102 }
103}
104
105pub fn android_clang_for(abi: &str, api: u32) -> Result<PathBuf> {
111 let bin = ndk_bin_dir()?;
112 let prefix = clang_target_prefix(abi)?;
113 let clang = bin.join(format!("{prefix}{api}-clang"));
114 if !clang.exists() {
115 anyhow::bail!(
116 "NDK clang not found: {} — check that the NDK is installed and \
117 API level {api} is supported",
118 clang.display(),
119 );
120 }
121 Ok(clang)
122}
123
124pub fn ndk_bin_dir() -> Result<PathBuf> {
128 let ndk = ndk_home()?;
129 let host = host_tag()?;
130 Ok(ndk.join("toolchains/llvm/prebuilt").join(host).join("bin"))
131}
132
133pub fn abi_from_jni_libs_path(p: &Path) -> Option<&'static str> {
139 let parent = p.parent()?.file_name()?.to_str()?;
140 match parent {
141 "arm64-v8a" => Some("arm64-v8a"),
142 "armeabi-v7a" => Some("armeabi-v7a"),
143 "x86_64" => Some("x86_64"),
144 "x86" => Some("x86"),
145 _ => None,
146 }
147}
148
149#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
160 fn host_tag_returns_a_known_string_for_this_host() {
161 let t = host_tag().expect("host tag");
162 assert!(matches!(
163 t,
164 "darwin-x86_64" | "linux-x86_64" | "windows-x86_64",
165 ));
166 }
167
168 #[test]
169 fn clang_target_prefix_maps_known_abis() {
170 assert_eq!(
171 clang_target_prefix("arm64-v8a").unwrap(),
172 "aarch64-linux-android"
173 );
174 assert_eq!(
175 clang_target_prefix("armeabi-v7a").unwrap(),
176 "armv7a-linux-androideabi"
177 );
178 assert_eq!(
179 clang_target_prefix("x86_64").unwrap(),
180 "x86_64-linux-android"
181 );
182 assert_eq!(clang_target_prefix("x86").unwrap(), "i686-linux-android");
183 }
184
185 #[test]
186 fn clang_target_prefix_rejects_unknown_abi() {
187 let err = clang_target_prefix("riscv64").unwrap_err();
188 assert!(format!("{err:#}").contains("unknown Android ABI"));
189 }
190
191 #[test]
192 fn abi_from_jni_libs_path_maps_known_layouts() {
193 assert_eq!(
194 abi_from_jni_libs_path(Path::new(
195 "/ws/examples/foo/android/app/src/main/jniLibs/arm64-v8a/libfoo.so",
196 )),
197 Some("arm64-v8a"),
198 );
199 assert_eq!(
200 abi_from_jni_libs_path(Path::new("/ws/jniLibs/x86_64/libfoo.so")),
201 Some("x86_64"),
202 );
203 }
204
205 #[test]
206 fn abi_from_jni_libs_path_returns_none_for_non_abi_layout() {
207 assert_eq!(
208 abi_from_jni_libs_path(Path::new("/random/path/libfoo.so")),
209 None,
210 );
211 assert_eq!(
212 abi_from_jni_libs_path(Path::new("/ws/jniLibs/unknown-abi/libfoo.so")),
213 None,
214 );
215 }
216
217 #[test]
220 fn android_clang_returns_a_path_when_ndk_is_installed() {
221 let Ok(ndk) = ndk_home() else { return };
224 let clang = android_clang_for("arm64-v8a", 21).expect("ndk clang");
225 assert!(clang.starts_with(&ndk));
226 assert!(clang.is_file(), "{clang:?} should exist");
227 }
228}