Sunday, December 6, 2009

Fat iPhone Static Libraries: One File = Device + Simulator Code

Abstract
This post explains how to build a static iPhone library that contains ARM code (for iPhones and iPod Touches) and x86 code (for the simulator), in a single file.

This is useful for companies which do not wish to expose their source code when distributing iPhone code, e.g. AdMob. The currently accepted practice is to distribute separate static library files for devices and for the simulator. This means the that the library clients have to setup separate targets for the simulator and device. The separate targets duplicate most information, and only differ in the static libraries included. This violates the DRY principle,

Solution

Open a Terminal, cd into your library's Xcode project directory, and issue the following commands.
xcodebuild -sdk iphoneos3.0 "ARCHS=armv6 armv7" clean build
xcodebuild -sdk iphonesimulator3.0 "ARCHS=i386 x86_64" "VALID_ARCHS=i386 x86_64" clean build
lipo -output build/libClosedLib.a -create build/Release-iphoneos/libClosedLib.a build/Release-iphonesimulator/libClosedLib.a

The commands above assume your library's name is ClosedLib and you're targeting iPhone OS 3.0. It should be straightforward to tweak the commands to accomodate different library names and SDK versions.


Assumptions
I assume that the library is not updated too often, so a couple of manual steps are acceptable. I would like to make add an automatic build feature to my zerg-xcode tool, but there is no ETA for that. I also assume that your project's default target is the static library.

I do not assume that the device and simulator SDKs have similar headers. The beauty of my method is that the simulator code is built with the simulator SDK, and the device code is build with the device SDK.

Explanation
The format used in Mac OS X and iPhone OS libraries and executable files is Mach-O. The format supports "fat" binaries, which are pretty much concatenated Mach-O files for different architectures, with a thin header.

Xcode can build fat binaries. In fact, if you set Architectures to Optimized in your project's build options, you'll get a fat binary containing both ARMv6 (iPhone 2G + 3G, iPod Touch) and ARMv7 (iPhone 3GS, iPod Touch 2G + 3G) code.

Xcode's output is controlled by the options ARCH (Architectures) and VALID_ARCH (Valid architectures). The architectures that actually get built are the intersections of these two options. Due to the different ARM processors in iPhones, device builds have VALID_ARCH set to include ARMv6 and ARMv7. However, simulator builds only target the i386 platform. I want a fat binary for the simulator as well, so I change the VALID_ARCH option to include the AMD64 platform.

The last step in the process is the lipo tool that comes with the Xcode Tools, and manages fat binaries. I use it to create a fat binary containing the union of the architectures in the two static libraries. The device build contributes the ARM architectures, and the simulator build contributes the Intel architectures.

The build process can be tweaked to throw out the AMD64 code, but I wanted to avoid hard-coding processor constants. Most importantly, using a fat library does not translate to larger iPhone applications, because the GNU linker strips out unused architectures when building binaries.

Testing
I tested this method by creating a iPhone static library, and an iPhone Window Application. I built the library using the steps above, and I included the headers and static library in the application. Then I used the debugger to confirm that the library code works both on the simulator and on an iPod Touch 2G.

My solution was only tested with the latest version of Xcode at the time of this writing (Xcode 3.1.2), but it should work on any version of Xcode 3, assuming no bugs come up. I tested on Snow Leopard, but the Leopard version of the iPhone SDK should work as well.

References
Mac Dev Center: Mac OS X ABI Reference
Amit Singh: Mac OS X Internals

8 comments:

  1. What the hell is this, i was waiting for a way that expains how i can create an iphone static library by showing the steps that should be done on xcode.....

    ReplyDelete
  2. @Anonymous: you can find a step-by-step guide for creating static libraries with Xcode here: http://blog.stormyprods.com/2008/11/using-static-libraries-with-iphone-sdk.html

    I didn't include that information because I did not discover it. I only came up with a way to get device and simulator builds together in one file, for Xcode 3.2 and later.

    ReplyDelete
  3. @Anonymous: who are you to complain? The author has provided a great resource to people that need it. If it's not what you want, you should consider asking more politely for the author to spend some of their time to help you.

    Or you could just use google, the information that you are looking for has been available since the middle of last year, and wasn't even hard to find.

    ReplyDelete
  4. This is amazing. It worked. Is there anything i have to do different for Universal binaries for iPhone OS 3.0 and iPhone OS 3.2/4.0?

    ReplyDelete
  5. THANK YOU! Thank you! Thank you! This was exactly what I was looking for. How the heck you figured it out is beyond me, but I'm glad you did. I'm now using this technique on the latest iOS 4 SDK and it works perfectly. Thanks again.

    ReplyDelete
  6. Thank you very much, it is exactly what I am looking for.

    ReplyDelete
  7. This was awesome, it worked perfectly for me, thanks so much. I'm trying to figure out how to find the header files with role 'Public' inside the library from the host project. I have a question on this open at Stack Overflow:

    http://stackoverflow.com/questions/4564063/how-can-i-build-an-objective-c-static-library-to-distribute-as-a-single-binary-an

    Feel free to chime in if you have any insight!

    ReplyDelete