Skip to content

Commit e77bccf

Browse files
committed
Export NDK libclang path for bindgen
Signed-off-by: Matthew Stanton <stantonmatthewj@gmail.com>
1 parent 00800b7 commit e77bccf

2 files changed

Lines changed: 158 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ These environment variables are exported for use in build scripts and other down
126126
| `CARGO_NDK_SYSROOT_PATH` | Path to the sysroot inside the Android NDK | `/path/to/ndk/toolchains/llvm/prebuilt/...` |
127127
| `CARGO_NDK_SYSROOT_TARGET` | The target name for files inside the sysroot (differs from LLVM triples) | `aarch64-linux-android` |
128128
| `CARGO_NDK_SYSROOT_LIBS_PATH` | Path to libraries in sysroot with target (`$CARGO_NDK_SYSROOT_PATH/usr/lib/$CARGO_NDK_SYSROOT_TARGET`) | `/path/to/ndk/.../usr/lib/aarch64-linux-android` |
129+
| `LIBCLANG_PATH` | Path containing the NDK's libclang shared library, when detected | `/path/to/ndk/toolchains/llvm/prebuilt/.../lib` |
129130
| `ANDROID_PLATFORM` | The platform version being used | `21` |
130131
| `ANDROID_ABI` | The Android ABI name | `armeabi-v7a` |
131132

src/cargo.rs

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,48 @@ fn clang_lib_path(ndk_home: &Path) -> PathBuf {
5858
.join("linux")
5959
}
6060

61+
#[inline]
62+
fn contains_libclang(path: &Path) -> bool {
63+
std::fs::read_dir(path)
64+
.ok()
65+
.into_iter()
66+
.flat_map(|entries| entries.filter_map(|entry| entry.ok()))
67+
.any(|entry| {
68+
entry
69+
.file_name()
70+
.to_str()
71+
.is_some_and(|name| name.starts_with("libclang."))
72+
})
73+
}
74+
75+
#[inline]
76+
fn libclang_search_suffixes() -> &'static [&'static str] {
77+
if cfg!(target_env = "musl") {
78+
&["musl/lib", "lib", "lib64", "bin"]
79+
} else {
80+
&["lib", "lib64", "bin", "musl/lib"]
81+
}
82+
}
83+
84+
#[inline]
85+
fn libclang_path_with_suffixes(ndk_home: &Path, suffixes: &[&str]) -> Option<PathBuf> {
86+
let prebuilt_path = ndk_home
87+
.join("toolchains")
88+
.join("llvm")
89+
.join("prebuilt")
90+
.join(ARCH);
91+
92+
suffixes
93+
.iter()
94+
.map(|suffix| prebuilt_path.join(suffix))
95+
.find(|path| contains_libclang(path))
96+
}
97+
98+
#[inline]
99+
fn libclang_path(ndk_home: &Path) -> Option<PathBuf> {
100+
libclang_path_with_suffixes(ndk_home, libclang_search_suffixes())
101+
}
102+
61103
const CARGO_NDK_SYSROOT_PATH_KEY: &str = "CARGO_NDK_SYSROOT_PATH";
62104
const CARGO_NDK_SYSROOT_TARGET_KEY: &str = "CARGO_NDK_SYSROOT_TARGET";
63105
const CARGO_NDK_SYSROOT_LIBS_PATH_KEY: &str = "CARGO_NDK_SYSROOT_LIBS_PATH";
@@ -168,6 +210,10 @@ pub(crate) fn build_env(
168210
.into_iter()
169211
.collect::<BTreeMap<String, OsString>>();
170212

213+
if let Some(path) = libclang_path(ndk_home) {
214+
envs.insert("LIBCLANG_PATH".to_string(), path.into_os_string());
215+
}
216+
171217
// cmd.arg(format!("-L{builtins_path}"));
172218

173219
// // Extract arch from target triple (e.g., "aarch64-linux-android21" -> "aarch64")
@@ -370,11 +416,43 @@ fn append_target_args(
370416

371417
#[cfg(test)]
372418
mod tests {
373-
use std::{ffi::OsString, path::Path};
419+
use std::{
420+
ffi::OsString,
421+
fs,
422+
path::{Path, PathBuf},
423+
time::{SystemTime, UNIX_EPOCH},
424+
};
374425

375426
use cargo_metadata::semver::Version;
376427

377-
use super::{append_target_args, build_env};
428+
use crate::ARCH;
429+
430+
use super::{
431+
append_target_args, build_env, libclang_path, libclang_path_with_suffixes,
432+
libclang_search_suffixes,
433+
};
434+
435+
fn temp_ndk_path(name: &str) -> PathBuf {
436+
let nanos = SystemTime::now()
437+
.duration_since(UNIX_EPOCH)
438+
.expect("system clock before Unix epoch")
439+
.as_nanos();
440+
let path = std::env::temp_dir().join(format!("cargo-ndk-{name}-{nanos}"));
441+
fs::remove_dir_all(&path).ok();
442+
path
443+
}
444+
445+
fn create_libclang_file(ndk_home: &Path, suffix: &str) -> PathBuf {
446+
let path = ndk_home
447+
.join("toolchains")
448+
.join("llvm")
449+
.join("prebuilt")
450+
.join(ARCH)
451+
.join(suffix);
452+
fs::create_dir_all(&path).expect("failed to create fake libclang directory");
453+
fs::write(path.join("libclang.so"), "").expect("failed to write fake libclang");
454+
path
455+
}
378456

379457
#[test]
380458
fn build_env_exports_android_platform_and_abi() {
@@ -442,4 +520,81 @@ mod tests {
442520
]
443521
);
444522
}
523+
524+
#[test]
525+
fn libclang_path_prefers_host_ndk_layout() {
526+
let ndk_home = temp_ndk_path("host-libclang");
527+
let host_path = create_libclang_file(&ndk_home, "lib");
528+
let _fallback_path = create_libclang_file(&ndk_home, "musl/lib");
529+
530+
assert_eq!(libclang_path(&ndk_home), Some(host_path));
531+
532+
fs::remove_dir_all(ndk_home).ok();
533+
}
534+
535+
#[test]
536+
fn libclang_path_prefers_musl_layout_for_musl_hosts() {
537+
let ndk_home = temp_ndk_path("musl-host-libclang");
538+
let _host_path = create_libclang_file(&ndk_home, "lib");
539+
let musl_path = create_libclang_file(&ndk_home, "musl/lib");
540+
541+
assert_eq!(
542+
libclang_path_with_suffixes(&ndk_home, &["musl/lib", "lib", "lib64", "bin"]),
543+
Some(musl_path)
544+
);
545+
546+
fs::remove_dir_all(ndk_home).ok();
547+
}
548+
549+
#[test]
550+
fn libclang_path_falls_back_to_lib64_ndk_layout() {
551+
let ndk_home = temp_ndk_path("lib64-libclang");
552+
let fallback_path = create_libclang_file(&ndk_home, "lib64");
553+
554+
assert_eq!(libclang_path(&ndk_home), Some(fallback_path));
555+
556+
fs::remove_dir_all(ndk_home).ok();
557+
}
558+
559+
#[test]
560+
fn libclang_path_uses_musl_layout_as_last_resort() {
561+
let ndk_home = temp_ndk_path("musl-libclang");
562+
let fallback_path = create_libclang_file(&ndk_home, "musl/lib");
563+
564+
assert_eq!(libclang_path(&ndk_home), Some(fallback_path));
565+
566+
fs::remove_dir_all(ndk_home).ok();
567+
}
568+
569+
#[test]
570+
fn libclang_search_order_matches_host_abi() {
571+
let suffixes = libclang_search_suffixes();
572+
573+
if cfg!(target_env = "musl") {
574+
assert_eq!(suffixes[0], "musl/lib");
575+
} else {
576+
assert_eq!(suffixes[0], "lib");
577+
}
578+
}
579+
580+
#[test]
581+
fn build_env_exports_libclang_path_when_detected() {
582+
let ndk_home = temp_ndk_path("build-env-libclang");
583+
let libclang_path = create_libclang_file(&ndk_home, "musl/lib");
584+
585+
let env = build_env(
586+
"aarch64-linux-android",
587+
&ndk_home,
588+
&Version::new(28, 0, 0),
589+
"--target=aarch64-linux-android28",
590+
28,
591+
"arm64-v8a",
592+
false,
593+
false,
594+
);
595+
596+
assert_eq!(PathBuf::from(env["LIBCLANG_PATH"].clone()), libclang_path);
597+
598+
fs::remove_dir_all(ndk_home).ok();
599+
}
445600
}

0 commit comments

Comments
 (0)