I’ve just updated my epic stack overflow answer with the full script + instructions for Xcode 4.
Here’s a quick summary without all the question/answer stuff from the SO page.
We can – theoretically – build a static library into a SINGLE FILE that includes: simulator, iPhone, and iPad.
Advantages
- just ONE FILE to ship to your developers / clients for including in their apps
- never accidentally send “the wrong” file, and have them complain of build failures
- when you update, they only have to overwrite one file, in one place, not two (I’ve noticed that with two files, 3rd party devs often accidentally only overwrite one of them, and we start to see version-mismatch problems)
- no mucking about switching back-and-forth between builds / schemes: just hit “build”, and you’re done. Easy!
However, Apple has no documentation on this that I can find, and Xcode’s default templates are NOT configured to do this. So, with some help from Eonil on SO, I built a script that you can copy/paste into an Xcode project, and which does this for you.
NB: I’ve only just started using Xcode4, and I suspect there MAY be a way to achieve this in the Xcode4 GUI. It used to be possible in Xcode3, and then one day Apple removed the feature entirely, with no warning. So, even if it exists in Xcode 4 today, it may disappear again. This script is your backup plan
.
Usage – Xcode 4
1. Create a static lib project
2. In the main sidebar, select the xcodeproject line
3. Select the TARGET that appears in a new sub-sidebar
4. Xcode 4.4 onwards Click “Add Build Phase”
5. Xcode 4.4 onwards Select “Add Copy Headers”
6. Click “Add Build Phase” (it’s hiding on bottom right corner of screen)
7. Select “Add Run Script”
8. Expand the new “Run Script” box that appeared as last item in the list on screen
9. Copy/paste the script below into the box
2012: Xcode 4.4 onwards: Latest Xcode has broken the creation of new libraries. Apple no longer creates the headers phase, which is ESSENTIAL to all libraries – until they fix Xcode, you have to add this manually. Make sure you add it ABOVE the “Run Script” phase.
Usage – Xcode 3
1. Create a static lib project
2. Select the Target
3. right-click and “Add … New Build Phase … New Run Script Build Phase”
4. Copy/paste the script below into the box
ALSO note: it doesn’t matter whether you select “Simulator” or “Device” in the build settings – this script automatically RE-builds the other one for you, and puts everything together in a single binary
Finding the library
Read the output from the Build (you may have to select “All messages” in Xcode 4, or it will just say “success!”) – the last line tells you exactly where to find your library.
Script
I’d recommend doing a copy/paste directly from the StackOverflow page – that’s where I will update it with any improvements in future. However, in case that page ever disappears, here’s the latest version as of March 2011:
# Version 2.1
# Changes:
# - Added Carlos fixes for storing multiple Xcode Projects in a single folder (although you're brave if you do that - Apple doesn't support it well!)
#
# Purpose:
# Create a static library for iPhone from within XCode
# Because Apple staff DELIBERATELY broke Xcode to make this impossible from the GUI (Xcode 3.2.3 specifically states this in the Release notes!)
# ...no, I don't understand why they did this!
#
# Author: Adam Martin - http://twitter.com/redglassesapps
# Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER)
#
# More info: see this Stack Overflow question: http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4
#################[ Tests: helps workaround any future bugs in Xcode ]########
#
DEBUG_THIS_SCRIPT="false"
if [ $DEBUG_THIS_SCRIPT = "true" ]
then
echo "########### TESTS #############"
echo "Use the following variables when debugging this script; note that they may change on recursions"
echo "BUILD_DIR = $BUILD_DIR"
echo "BUILD_ROOT = $BUILD_ROOT"
echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR"
echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR"
echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR"
echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR"
fi
#####################[ part 1 ]##################
# First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it)
# (incidental: searching for substrings in sh is a nightmare! Sob)
SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$')
# Next, work out if we're in SIM or DEVICE
if [ ${PLATFORM_NAME} = "iphonesimulator" ]
then
OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION}
else
OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION}
fi
echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})"
echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}"
#
#####################[ end of part 1 ]##################
#####################[ part 2 ]##################
#
# IF this is the original invocation, invoke WHATEVER other builds are required
#
# Xcode is already building ONE target...
#
# ...but this is a LIBRARY, so Apple is wrong to set it to build just one.
# ...we need to build ALL targets
# ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!)
#
#
# So: build ONLY the missing platforms/configurations.
if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse"
else
# CRITICAL:
# Prevent infinite recursion (Xcode sucks)
export ALREADYINVOKED="true"
echo "RECURSION: I am the root ... recursing all missing build targets NOW..."
echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO"
xcodebuild -project “${PROJECT_FILE_PATH} -configuration "${CONFIGURATION}" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
ACTION="build"
#Merge all platform binaries as a fat binary for each configurations.
# Calculate where the (multiple) built files are coming from:
CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos
CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator
echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}"
echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}"
CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
echo "...I will output a universal build to: ${CREATING_UNIVERSAL_DIR}"
# ... remove the products of previous runs of this script
# NB: this directory is ONLY created by this script - it should be safe to delete!
# Carlos Aragones change: delete just the generated executable, not the whole directory
# NOTE: UNTESTED
#
mkdir -p “${CREATING_UNIVERSAL_DIR}”
rm -rf “${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}”
#
echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"
lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"
#########
#
# Added: StackOverflow suggestion to also copy "include" files
# (untested, but should work OK)
#
if [ -d "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include" ]
then
mkdir -p "${CREATING_UNIVERSAL_DIR}/usr/local/include"
cp "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include/*" "${CREATING_UNIVERSAL_DIR}/usr/local/include"
fi
fi