How to create universal static libraries on Xcode 4: the traditional way

by borealkiss

[Note on 09.04.2011: This article will not be helpful unless you really need universal static libraries for distribution purposes etc. Instead, using workspace is a better way for daily development.]

INTRODUCTION

Creating static libraries universal for iPhone device and simulator is a little bit tricky. On Xcode 3.x, the major way takes something like the following steps:

  • Create a build for the device (the armv6 and armv7 architecture).
  • Create a build for the simulator (the i386 architecture).
  • Merge the above builds to create a universal build using lipo.

Since Xcode, 4 a new command called archiving (Product > Archive) is introduced as a way to distribute applications and/or static libraries. At glance this is suitable to creating a universal libraries but is not. One can not select this option for the simulator environment (you will notice it is always grayed out). As such we still have to rely on the traditional way to create universal libraries (1).

In this article I will show you how to create such a fat static library on Xcode 4. Because the place for product builds of each project are completely changed from those of Xcode 3.x, we have to make a slight effort tailoring it to the Xcode 4.

HOW TO DO

In the following example I will use my project called BKMovableVC. On your workspace you can use whatever name you want, of course.

STEP 1

Create a new Cocoa Touch Static Library target (File > New > New Target > Framework & Library) for the simulator environment. Naming is arbitrary as long as you can recognize (I named here BKMovableVC-iphonesimulator). Add related source files to the Compile Source pane and headers to Copy Headerpane. For headers you can choose three possible places to put in, Public , Private, and Project (default is Project). For example if you choose Public, all headers will be automatically copied to the build directory, which is easy for me to continue following works.

[Note on 04.05.2011: There is a bug on Xcode 4; when all panes (Public, Private, and Project) in the Copy Header pane are closed or never opened before, pushing the add button causes a crash. Open one of them before adding files.]

From the Edit Scheme pane (Product > Edit Scheme), change its build configuration to Release. This enables you to create a Release build product once the build operation is done. This build operation supposes to be done on the simulator architecture (i386).

STEP 2

Create a new static library target for the iPhone device environment. Naming is arbitrary but must be different from that of the simulator (I named here BKMovableVC-iphoneos). Add related source files and headers and change its build configuration to Release. Every step is just the same as that of the simulator except for the build architecture; this build operation supposes to be done on the device architecture (armv6 and armv7).

Now we have two targets, one for the device and the other for the simulator. When you build a product, the product will be placed under the following directory:

~/Library/Developer/Xcode/DerivedData/

This place is shared by build products of all projects created on Xcode 4, which is quite different from the legacy place found on Xcode 3.x (each project has its own build directory). For example, if the name of the project is BKMovableVC, the Release build product for the simulator will be placed at

~/Library/Developer/Xcode/DerivedData/BKMovableVC-xxxxxxx/
        Build/Products/Release-iphonesimulator/
        libBKMovableVC-iphonesimulator.a

and that for the device will be found at

~/Library/Developer/Xcode/DerivedData/BKMovableVC-xxxxxxx/
        Build/Products/Release-iphoneos/
        libBKMovableVC-iphoneos.a

where BKMovableVC-xxxxxxx is a unique directory name automatically assigned by Xcode 4.

STEP 3

Let’s merge them using the lipo command. Create a new Aggregate target (File > New > New Target > Other). I named it here BKMovableVC-ios4.3-0.9. On the Build Pheses pane select Add Run Script from the Add Build Phase button on the bottom-left corner.

Now we are about to run some scripts on the Run Script pane but here is a problem. How do we direct lipo to work with builds that are placed under the directory whose name is automatically assigned by Xcode 4 (e.g., BKMovableVC-xxxxxxx for the above case)? Fortunately it is easily solved by the Xcode 4 environment variable called ${BUILT_PRODUCTS_DIR}. This variable represents the path to the directory for a current build product. For example, when we create a Release build product for the simulator explained above, ${BUILT_PRODUCTS_DIR} corresponds to the following path:

~/Library/Developer/Xcode/DerivedData/BKMovableVC-xxxxxxx/
        Build/Products/Release-iphonesimulator/

Let’s continue. Write the following script on the Run Script pane. This lets lipo create a merged build product called libBKMovableVC-ios4.3-0.9.a (you can give it whatever name you want). If there is already the product with the same name lipo deletes it before the merge operation as shown in the first line.

rm -rf ${BUILT_PRODUCTS_DIR}/libBKMovableVC-ios4.3-0.9.a
 
lipo -create "${BUILT_PRODUCTS_DIR}/../${BUILD_STYLE}-iphonesimulator/libBKMovableVC-iphonesimulator.a" /
"${BUILT_PRODUCTS_DIR}/libBKMovableVC-iphoneos.a" -output /
"${BUILT_PRODUCTS_DIR}/libBKMovableVC-ios4.3-0.9.a"

Here is one trick. In this script the path representation to previously created build products such as libBKMovableVC-iphonesimulator.a looks odd because of “/../”. This is because the environment variable ${BUILT_PRODUCTS_DIR} depends on a current build phase (iPhone device, simulator, Debug, Release). For example, if this script runs for the Release build of iPhone device, the variable ${BUILT_PRODUCTS_DIR} will represent