Falko's Blog

Building a fat / universal library for macOS

It’s that time of the decade1 again and Apple switched to a different processor architecture. A good opportunity to look into how to build a static library that runs not only on ARM (Apple Silicon / M1) and Intel, but also on older macOS versions that require a different SDK. Also a good opportunity to take some notes, so I remember how to do this when the next switch happens. 😅

In the article I will use the terms “fat library” and “universal library” interchangably.

Basic universal library

The easiest way to build a fat / universal library is to use the --arch flag of the compiler more than once. When building a standard c library based on configure/make, we set the CFLAGS variable to have the right --arch flags. Everything else works just as usual:

$ CFLAGS="-arch arm64 -arch x86_64" ./configure --prefix=/some/build/dir
$ make
$ make install

To check if this worked, we can use the lipo tool:

$ cd /some/build/dir/lib
$ lipo -info libz.a
Architectures in the fat file: libz.a are: x86_64 arm64

As you can see, libz.a has x86_64 and arm64 code in it. Done! 🎉

Supporting different macOS versions

This is the more interesting scenario, where we want our static library to work on ARM as well as on Intel. At the same time, it should be built against an older macOS SDK (e.g. macOS X 10.10). Since these older SDKs do not support ARM, they can only be used with the Intel version.

The solution is to build the library twice, once for each architecture/SDK combination, and then to use lipo to merge the separate libraries into a fat library. The compiler/linker flag -isysroot is used to set the path to the SDK.

Here are the separate steps:

Intel version

$ CFLAGS="-arch x86_64 -isysroot /path/to/MacOSX10.10.sdk" \
  LDFLAGS="-isysroot /path/to/MacOSX10.10.sdk" \
  ./configure --prefix=/some/build/dir-intel
$ make
$ make install

ARM version

$ make clean

$ CFLAGS="-arch arm64" \
  ./configure --prefix=/some/build/dir-arm
$ make
$ make install

Merge into fat library

$ lipo -create dir-intel/lib/libsomething.a dir-arm/lib/libsomething.a -output fat/lib/libsomething.a

And this is already it. The library created in fat/lib/libsomething.a will work on ARM and Intel processors as well as macOS X 10.10 or newer.

In case you are wondering how a typical crash report on an older macOS looks like when you use the wrong SDK for your library, here it is:

[...]

Dyld Error Message:
  Symbol not found: ____some_symbol
  Referenced from: /path/to/Awesome.app/Contents/MacOS/Awesome
  Expected in: /usr/lib/libSystem.B.dylib

[...]

Footnotes


  1. Apple transitioned from 68k to PPC in 1994 and from PPC to Intel in 2005: Mac transition to Intel processors ↩︎