@@ -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+
61103const CARGO_NDK_SYSROOT_PATH_KEY : & str = "CARGO_NDK_SYSROOT_PATH" ;
62104const CARGO_NDK_SYSROOT_TARGET_KEY : & str = "CARGO_NDK_SYSROOT_TARGET" ;
63105const 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) ]
372418mod 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