Cross Compiling a C Library for iOS and macOS

Architectures everywhere

Apple has a neat array of devices running macOS and iOS - each of which run on multiple architectures. While macOS is making the transition to arm64, we still have to support x86_64 in the interim. Multiple architectures on macOS necessitates building for the iPhone simulator on arm64 and x86_64 as well. This means we have to build for

Targets To The Rescue

Fortunately, providing clang with a target at invocation, makes it quite easy to build across platforms. The target is defined by a triple like arm64-apple-darwin. The first part of the triple refers to the target architecture. The second part refers to the vendor, while the third is the target system. The triples we need to use to target macOS and iOS are

Modify the iOS versions referenced in the triples based on what’s installed on your system. The second part to the cross compiling is to provide a sysroot - a place where clang can find the headers, libraries for the target platform.

Compiling for macOS x86_64 would require a command invocation like

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -target x86_64-apple-darwin -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -c -I /path/to/headers src/file_to_compile.c -o output_dir/compiled_file.o

Similar platform folders for iPhoneSimulator and iPhoneOS can be found under /Applications/Xcode.app/Contents/Developer/Platforms. We need to enable Bitcode as well, by passing in -fembed-bitcode-marker, while compiling for iPhoneOS.

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -target arm64-apple-ios15.5 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.5.sdk -fembed-bitcode-marker -c -I /path/to/headers src/file_to_compile.c -o output_dir/compiled_file.o

It’s important to use the version of clang that is bundled with Xcode.app (there might be one installed in /usr/bin as well). The one in /usr/bin will not be able to work with the platform files in sysroot.

Once, we have compiled the code into object files we can assemble them into static library files using llvm-ar. This might require an installation of llvm.

/opt/homebrew/opt/llvm/bin/llvm-ar rc output_dir/libMyLib.a
output_dir/compiled_file.o

Enter lipo

Even though macOS and iPhoneSimulator run on multiple architectures (arm64, x86_64), Apple considers them to be single platforms. So, we have to combine the libraries for each architecture into a single fat library before we use them in frameworks. After compiling the library for each architecture, we can lipo them.

lipo -create static/ios_simulator_x86_64/libMyLib.a static/ios_simulator_arm64/libMyLib.a -output static/ios_simulator_universal/libMyLib.a

With libraries compiled (and lipoed as needed) for macOS, iPhoneSimulator and iPhoneOS, we can quite easily assemble them into a binary .xcframework.