@@ -96,6 +96,68 @@ fn copy_folder(src: &Path, dst: &Path) {
9696 }
9797}
9898
99+ /// Extract a specific architecture from a fat binary for iOS
100+ /// Returns the path to the extracted single-architecture library
101+ fn extract_thin_lib_for_ios ( lib_path : & Path , target : & str , out_dir : & Path ) -> PathBuf {
102+ debug_log ! ( "Checking if {} is a fat binary" , lib_path. display( ) ) ;
103+
104+ // Check if it's a fat binary using lipo
105+ let lipo_info = Command :: new ( "lipo" )
106+ . arg ( "-info" )
107+ . arg ( lib_path)
108+ . output ( ) ;
109+
110+ if let Ok ( output) = lipo_info {
111+ let info_str = String :: from_utf8_lossy ( & output. stdout ) ;
112+ debug_log ! ( "lipo info: {}" , info_str) ;
113+
114+ // If it contains "Architectures in the fat file", it's a fat binary
115+ if info_str. contains ( "Architectures in the fat file" ) || info_str. contains ( "universal binary" ) {
116+ // Determine the target architecture
117+ let arch = if target. contains ( "x86_64" ) {
118+ "x86_64"
119+ } else if target. contains ( "aarch64" ) || target. contains ( "arm64" ) {
120+ "arm64"
121+ } else {
122+ debug_log ! ( "Unknown target architecture for {}" , target) ;
123+ return lib_path. to_path_buf ( ) ;
124+ } ;
125+
126+ debug_log ! ( "Extracting {} architecture from fat binary" , arch) ;
127+
128+ // Create output path for thin library
129+ // Use the original filename to avoid library name conflicts
130+ let file_name = lib_path. file_name ( ) . unwrap ( ) ;
131+ let thin_lib_path = out_dir. join ( file_name) ;
132+
133+ // Extract the specific architecture
134+ let extract_result = Command :: new ( "lipo" )
135+ . arg ( lib_path)
136+ . arg ( "-thin" )
137+ . arg ( arch)
138+ . arg ( "-output" )
139+ . arg ( & thin_lib_path)
140+ . status ( ) ;
141+
142+ match extract_result {
143+ Ok ( status) if status. success ( ) => {
144+ debug_log ! ( "Successfully extracted {} to {}" , arch, thin_lib_path. display( ) ) ;
145+ return thin_lib_path;
146+ }
147+ Ok ( status) => {
148+ debug_log ! ( "lipo failed with status: {}" , status) ;
149+ }
150+ Err ( e) => {
151+ debug_log ! ( "Failed to run lipo: {}" , e) ;
152+ }
153+ }
154+ }
155+ }
156+
157+ // If not a fat binary or extraction failed, return original path
158+ lib_path. to_path_buf ( )
159+ }
160+
99161fn extract_lib_names ( out_dir : & Path , is_dynamic : bool , target_os : & str ) -> Vec < String > {
100162 let lib_pattern = if target_os == "windows" {
101163 "*.lib"
@@ -197,6 +259,38 @@ fn macos_link_search_path() -> Option<String> {
197259 None
198260}
199261
262+ fn ios_link_search_path ( ) -> Option < String > {
263+ // Get the clang version
264+ let output = Command :: new ( "clang" )
265+ . arg ( "--version" )
266+ . output ( )
267+ . ok ( ) ?;
268+
269+ if !output. status . success ( ) {
270+ debug_log ! ( "failed to run 'clang --version'" ) ;
271+ return None ;
272+ }
273+
274+ let version_str = String :: from_utf8_lossy ( & output. stdout ) ;
275+ // Extract version number (e.g., "Apple clang version 17.0.0" -> "17")
276+ let version = version_str
277+ . lines ( )
278+ . next ( ) ?
279+ . split_whitespace ( )
280+ . find_map ( |s| {
281+ if s. chars ( ) . next ( ) ?. is_digit ( 10 ) {
282+ Some ( s. split ( '.' ) . next ( ) ?. to_string ( ) )
283+ } else {
284+ None
285+ }
286+ } ) ?;
287+
288+ Some ( format ! (
289+ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/{}/lib/darwin" ,
290+ version
291+ ) )
292+ }
293+
200294fn rerun_on_env_changes ( vars : & [ & str ] ) {
201295 for env in vars {
202296 println ! ( "cargo::rerun-if-env-changed={env}" ) ;
@@ -407,10 +501,26 @@ fn main() {
407501
408502 debug_log ! ( "dist libs: {:?}" , dist. libs) ;
409503 if let Some ( libs) = dist. libs {
410- for lib in libs. iter ( ) {
411- let lib_path = cache_dir. join ( lib) ;
412- let lib_parent = lib_path. parent ( ) . unwrap ( ) ;
413- add_search_path ( lib_parent) ;
504+ // For iOS, we need to handle fat binaries (universal binaries with multiple architectures)
505+ // Extract the specific architecture needed for the target
506+ if target. contains ( "ios" ) {
507+ debug_log ! ( "Processing iOS libraries, checking for fat binaries" ) ;
508+ for lib in libs. iter ( ) {
509+ let lib_path = cache_dir. join ( lib) ;
510+
511+ // Extract thin library if it's a fat binary
512+ let processed_lib_path = extract_thin_lib_for_ios ( & lib_path, & target, & out_dir) ;
513+
514+ // Add the directory containing the processed library to search path
515+ let lib_parent = processed_lib_path. parent ( ) . unwrap ( ) ;
516+ add_search_path ( lib_parent) ;
517+ }
518+ } else {
519+ for lib in libs. iter ( ) {
520+ let lib_path = cache_dir. join ( lib) ;
521+ let lib_parent = lib_path. parent ( ) . unwrap ( ) ;
522+ add_search_path ( lib_parent) ;
523+ }
414524 }
415525
416526 sherpa_libs = libs
@@ -510,13 +620,54 @@ fn main() {
510620 link_lib ( "msvcrtd" , true ) ;
511621 }
512622
513- // macOS
623+ // macOS and iOS common frameworks
514624 if target_os == "macos" || target_os == "ios" {
515625 link_framework ( "CoreML" ) ;
516626 link_framework ( "Foundation" ) ;
517627 link_lib ( "c++" , true ) ;
518628 }
519629
630+ // iOS specific configuration
631+ if target_os == "ios" {
632+ // Set minimum iOS deployment target to avoid linker errors
633+ // This prevents "___isPlatformVersionAtLeast" undefined symbol errors
634+ println ! ( "cargo:rustc-link-arg=-Wl,-platform_version,ios,13.0,13.0" ) ;
635+
636+ // Add rpath for finding dependencies
637+ println ! ( "cargo:rustc-link-arg=-Wl,-rpath,@executable_path" ) ;
638+
639+ // Link clang runtime for iOS to resolve symbols like ___chkstk_darwin
640+ if let Some ( clang_path) = ios_link_search_path ( ) {
641+ debug_log ! ( "iOS clang runtime path: {}" , clang_path) ;
642+
643+ // Choose the correct runtime library based on target
644+ let clang_rt_lib = if target. contains ( "sim" ) {
645+ "libclang_rt.iossim.a"
646+ } else {
647+ "libclang_rt.ios.a"
648+ } ;
649+
650+ let clang_rt_path = Path :: new ( & clang_path) . join ( clang_rt_lib) ;
651+
652+ // Extract thin library from fat binary
653+ let thin_clang_rt = extract_thin_lib_for_ios ( & clang_rt_path, & target, & out_dir) ;
654+
655+ // Add the directory containing the thin library to search path
656+ let clang_rt_parent = thin_clang_rt. parent ( ) . unwrap ( ) ;
657+ add_search_path ( clang_rt_parent) ;
658+
659+ // Extract lib name for linking
660+ let lib_name = thin_clang_rt
661+ . file_stem ( )
662+ . and_then ( |s| s. to_str ( ) )
663+ . and_then ( |s| s. strip_prefix ( "lib" ) )
664+ . unwrap_or ( "clang_rt.ios" ) ;
665+
666+ debug_log ! ( "Linking {}" , lib_name) ;
667+ link_lib ( lib_name, false ) ;
668+ }
669+ }
670+
520671 // Linux
521672 if target_os == "linux" || target == "android" {
522673 link_lib ( "stdc++" , true ) ;
0 commit comments