Skip to content

Commit a84f398

Browse files
authored
Merge pull request #782 from 4j17h/main
feat(android): enhance Android NDK support and validation
2 parents b107baa + 9a07697 commit a84f398

File tree

1 file changed

+251
-29
lines changed

1 file changed

+251
-29
lines changed

llama-cpp-sys-2/build.rs

Lines changed: 251 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ fn parse_target_os() -> Result<(TargetOs, String), String> {
4545
} else {
4646
Ok((TargetOs::Apple(AppleVariant::Other), target))
4747
}
48-
} else if target.contains("android") {
48+
} else if target.contains("android")
49+
|| target == "aarch64-linux-android"
50+
|| target == "armv7-linux-androideabi"
51+
|| target == "i686-linux-android"
52+
|| target == "x86_64-linux-android"
53+
{
54+
// Handle both full android targets and short names like arm64-v8a that cargo ndk might use
4955
Ok((TargetOs::Android, target))
5056
} else if target.contains("linux") {
5157
Ok((TargetOs::Linux, target))
@@ -162,6 +168,28 @@ fn macos_link_search_path() -> Option<String> {
162168
None
163169
}
164170

171+
fn validate_android_ndk(ndk_path: &str) -> Result<(), String> {
172+
let ndk_path = Path::new(ndk_path);
173+
174+
if !ndk_path.exists() {
175+
return Err(format!(
176+
"Android NDK path does not exist: {}",
177+
ndk_path.display()
178+
));
179+
}
180+
181+
let toolchain_file = ndk_path.join("build/cmake/android.toolchain.cmake");
182+
if !toolchain_file.exists() {
183+
return Err(format!(
184+
"Android NDK toolchain file not found: {}\n\
185+
This indicates an incomplete NDK installation.",
186+
toolchain_file.display()
187+
));
188+
}
189+
190+
Ok(())
191+
}
192+
165193
fn is_hidden(e: &DirEntry) -> bool {
166194
e.file_name()
167195
.to_str()
@@ -233,7 +261,7 @@ fn main() {
233261
);
234262

235263
// Bindings
236-
let bindings = bindgen::Builder::default()
264+
let mut bindings_builder = bindgen::Builder::default()
237265
.header("wrapper.h")
238266
.clang_arg(format!("-I{}", llama_src.join("include").display()))
239267
.clang_arg(format!("-I{}", llama_src.join("ggml/include").display()))
@@ -244,7 +272,149 @@ fn main() {
244272
.allowlist_type("ggml_.*")
245273
.allowlist_function("llama_.*")
246274
.allowlist_type("llama_.*")
247-
.prepend_enum_name(false)
275+
.prepend_enum_name(false);
276+
277+
// Configure Android-specific bindgen settings
278+
if matches!(target_os, TargetOs::Android) {
279+
// Detect Android NDK from environment variables
280+
let android_ndk = env::var("ANDROID_NDK")
281+
.or_else(|_| env::var("ANDROID_NDK_ROOT"))
282+
.or_else(|_| env::var("NDK_ROOT"))
283+
.or_else(|_| env::var("CARGO_NDK_ANDROID_NDK"))
284+
.or_else(|_| {
285+
// Try to auto-detect NDK from Android SDK
286+
if let Some(home) = env::home_dir() {
287+
let android_home = env::var("ANDROID_HOME")
288+
.or_else(|_| env::var("ANDROID_SDK_ROOT"))
289+
.unwrap_or_else(|_| format!("{}/Android/Sdk", home.display()));
290+
291+
let ndk_dir = format!("{}/ndk", android_home);
292+
if let Ok(entries) = std::fs::read_dir(&ndk_dir) {
293+
let mut versions: Vec<_> = entries
294+
.filter_map(|e| e.ok())
295+
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
296+
.filter_map(|e| e.file_name().to_str().map(|s| s.to_string()))
297+
.collect();
298+
versions.sort();
299+
if let Some(latest) = versions.last() {
300+
return Ok(format!("{}/{}", ndk_dir, latest));
301+
}
302+
}
303+
}
304+
Err(env::VarError::NotPresent)
305+
})
306+
.unwrap_or_else(|_| {
307+
panic!(
308+
"Android NDK not found. Please set one of: ANDROID_NDK, NDK_ROOT, ANDROID_NDK_ROOT\n\
309+
Current target: {}\n\
310+
Download from: https://developer.android.com/ndk/downloads",
311+
target_triple
312+
);
313+
});
314+
315+
// Get Android API level
316+
let android_api = env::var("ANDROID_API_LEVEL")
317+
.or_else(|_| env::var("ANDROID_PLATFORM").map(|p| p.replace("android-", "")))
318+
.or_else(|_| env::var("CARGO_NDK_ANDROID_PLATFORM").map(|p| p.replace("android-", "")))
319+
.unwrap_or_else(|_| "28".to_string());
320+
321+
// Determine host platform
322+
let host_tag = if cfg!(target_os = "macos") {
323+
"darwin-x86_64"
324+
} else if cfg!(target_os = "linux") {
325+
"linux-x86_64"
326+
} else if cfg!(target_os = "windows") {
327+
"windows-x86_64"
328+
} else {
329+
panic!("Unsupported host platform for Android NDK");
330+
};
331+
332+
// Map Rust target to Android architecture
333+
let android_target_prefix = if target_triple.contains("aarch64") {
334+
"aarch64-linux-android"
335+
} else if target_triple.contains("armv7") {
336+
"arm-linux-androideabi"
337+
} else if target_triple.contains("x86_64") {
338+
"x86_64-linux-android"
339+
} else if target_triple.contains("i686") {
340+
"i686-linux-android"
341+
} else {
342+
panic!("Unsupported Android target: {}", target_triple);
343+
};
344+
345+
// Setup Android toolchain paths
346+
let toolchain_path = format!("{}/toolchains/llvm/prebuilt/{}", android_ndk, host_tag);
347+
let sysroot = format!("{}/sysroot", toolchain_path);
348+
349+
// Validate toolchain existence
350+
if !std::path::Path::new(&toolchain_path).exists() {
351+
panic!(
352+
"Android NDK toolchain not found at: {}\n\
353+
Please ensure you have the correct Android NDK for your platform.",
354+
toolchain_path
355+
);
356+
}
357+
358+
// Find clang builtin includes
359+
let clang_builtin_includes = {
360+
let clang_lib_path = format!("{}/lib/clang", toolchain_path);
361+
std::fs::read_dir(&clang_lib_path).ok().and_then(|entries| {
362+
entries
363+
.filter_map(|e| e.ok())
364+
.find(|entry| {
365+
entry.file_type().map(|t| t.is_dir()).unwrap_or(false)
366+
&& entry
367+
.file_name()
368+
.to_str()
369+
.map(|name| name.chars().next().unwrap_or('0').is_ascii_digit())
370+
.unwrap_or(false)
371+
})
372+
.and_then(|entry| {
373+
let include_path =
374+
format!("{}/{}/include", clang_lib_path, entry.file_name().to_str()?);
375+
if std::path::Path::new(&include_path).exists() {
376+
Some(include_path)
377+
} else {
378+
None
379+
}
380+
})
381+
})
382+
};
383+
384+
// Configure bindgen for Android
385+
bindings_builder = bindings_builder
386+
.clang_arg(format!("--target={}", target_triple))
387+
.clang_arg(format!("--sysroot={}", sysroot))
388+
.clang_arg(format!("-D__ANDROID_API__={}", android_api))
389+
.clang_arg("-D__ANDROID__");
390+
391+
// Add include paths in correct order
392+
if let Some(ref builtin_includes) = clang_builtin_includes {
393+
bindings_builder = bindings_builder
394+
.clang_arg("-isystem")
395+
.clang_arg(builtin_includes);
396+
}
397+
398+
bindings_builder = bindings_builder
399+
.clang_arg("-isystem")
400+
.clang_arg(format!("{}/usr/include/{}", sysroot, android_target_prefix))
401+
.clang_arg("-isystem")
402+
.clang_arg(format!("{}/usr/include", sysroot))
403+
.clang_arg("-include")
404+
.clang_arg("stdbool.h")
405+
.clang_arg("-include")
406+
.clang_arg("stdint.h");
407+
408+
// Set additional clang args for cargo ndk compatibility
409+
if env::var("CARGO_SUBCOMMAND").as_deref() == Ok("ndk") {
410+
std::env::set_var(
411+
"BINDGEN_EXTRA_CLANG_ARGS",
412+
format!("--target={}", target_triple),
413+
);
414+
}
415+
}
416+
417+
let bindings = bindings_builder
248418
.generate()
249419
.expect("Failed to generate bindings");
250420

@@ -307,40 +477,92 @@ fn main() {
307477
config.static_crt(static_crt);
308478

309479
if matches!(target_os, TargetOs::Android) {
310-
// build flags for android taken from this doc
311-
// https://github.com/ggerganov/llama.cpp/blob/master/docs/android.md
480+
// Android NDK Build Configuration
312481
let android_ndk = env::var("ANDROID_NDK")
313-
.expect("Please install Android NDK and ensure that ANDROID_NDK env variable is set");
314-
315-
println!("cargo::rerun-if-env-changed=ANDROID_NDK");
482+
.or_else(|_| env::var("NDK_ROOT"))
483+
.or_else(|_| env::var("ANDROID_NDK_ROOT"))
484+
.unwrap_or_else(|_| {
485+
panic!(
486+
"Android NDK not found. Please set one of: ANDROID_NDK, NDK_ROOT, ANDROID_NDK_ROOT\n\
487+
Download from: https://developer.android.com/ndk/downloads"
488+
);
489+
});
316490

317-
config.define(
318-
"CMAKE_TOOLCHAIN_FILE",
319-
format!("{android_ndk}/build/cmake/android.toolchain.cmake"),
320-
);
321-
if env::var("ANDROID_PLATFORM").is_ok() {
322-
println!("cargo::rerun-if-env-changed=ANDROID_PLATFORM");
323-
} else {
324-
config.define("ANDROID_PLATFORM", "android-28");
491+
// Validate NDK installation
492+
if let Err(error) = validate_android_ndk(&android_ndk) {
493+
panic!("Android NDK validation failed: {}", error);
325494
}
326-
if target_triple.contains("aarch64") || target_triple.contains("armv7") {
327-
config.cflag("-march=armv8.7a");
328-
config.cxxflag("-march=armv8.7a");
495+
496+
// Rerun build script if NDK environment variables change
497+
println!("cargo:rerun-if-env-changed=ANDROID_NDK");
498+
println!("cargo:rerun-if-env-changed=NDK_ROOT");
499+
println!("cargo:rerun-if-env-changed=ANDROID_NDK_ROOT");
500+
501+
// Set CMake toolchain file for Android
502+
let toolchain_file = format!("{}/build/cmake/android.toolchain.cmake", android_ndk);
503+
config.define("CMAKE_TOOLCHAIN_FILE", &toolchain_file);
504+
505+
// Configure Android platform (API level)
506+
let android_platform = env::var("ANDROID_PLATFORM").unwrap_or_else(|_| {
507+
env::var("ANDROID_API_LEVEL")
508+
.map(|level| format!("android-{}", level))
509+
.unwrap_or_else(|_| "android-28".to_string())
510+
});
511+
512+
println!("cargo:rerun-if-env-changed=ANDROID_PLATFORM");
513+
println!("cargo:rerun-if-env-changed=ANDROID_API_LEVEL");
514+
config.define("ANDROID_PLATFORM", &android_platform);
515+
516+
// Map Rust target to Android ABI
517+
let android_abi = if target_triple.contains("aarch64") {
518+
"arm64-v8a"
519+
} else if target_triple.contains("armv7") {
520+
"armeabi-v7a"
329521
} else if target_triple.contains("x86_64") {
330-
config.cflag("-march=x86-64");
331-
config.cxxflag("-march=x86-64");
522+
"x86_64"
332523
} else if target_triple.contains("i686") {
333-
config.cflag("-march=i686");
334-
config.cxxflag("-march=i686");
524+
"x86"
335525
} else {
336-
// Rather than guessing just fail.
337-
panic!("Unsupported Android target {target_triple}");
526+
panic!(
527+
"Unsupported Android target: {}\n\
528+
Supported targets: aarch64-linux-android, armv7-linux-androideabi, i686-linux-android, x86_64-linux-android",
529+
target_triple
530+
);
531+
};
532+
533+
config.define("ANDROID_ABI", android_abi);
534+
535+
// Configure architecture-specific compiler flags
536+
match android_abi {
537+
"arm64-v8a" => {
538+
config.cflag("-march=armv8-a");
539+
config.cxxflag("-march=armv8-a");
540+
}
541+
"armeabi-v7a" => {
542+
config.cflag("-march=armv7-a");
543+
config.cxxflag("-march=armv7-a");
544+
config.cflag("-mfpu=neon");
545+
config.cxxflag("-mfpu=neon");
546+
config.cflag("-mthumb");
547+
config.cxxflag("-mthumb");
548+
}
549+
"x86_64" => {
550+
config.cflag("-march=x86-64");
551+
config.cxxflag("-march=x86-64");
552+
}
553+
"x86" => {
554+
config.cflag("-march=i686");
555+
config.cxxflag("-march=i686");
556+
}
557+
_ => {}
338558
}
559+
560+
// Android-specific CMake configurations
339561
config.define("GGML_LLAMAFILE", "OFF");
340-
if cfg!(feature = "shared-stdcxx") {
341-
println!("cargo:rustc-link-lib=dylib=stdc++");
342-
println!("cargo:rustc-link-lib=c++_shared");
343-
}
562+
563+
// Link Android system libraries
564+
println!("cargo:rustc-link-lib=log");
565+
println!("cargo:rustc-link-lib=android");
344566
}
345567

346568
if matches!(target_os, TargetOs::Linux)

0 commit comments

Comments
 (0)