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
Hi Adam,
Thanks for the wonderful post. I have created libMyfiles.a using the above procedure. But i could not link to my project. how do i do that?. can you guide my in linking this .a file ?. Thanks.
You should then just drag/drop that file from Finder into the “other” xcode project where you want to use the library.
Xcode *should* handle everything automatically from there – NB: I highly recommend checking the “copy into project if necessary”, so that the file is embedded in your project, rather than soft-linked.
That way, you can then safely delete / remove the .a file and not worry about accidentally breaking your app project. If you want to update the app to use a newer version of your library, you get to CHOOSE this, rather than have it happen autmatically without your ability to prevent / control it.
Hi adam,
i don know whether i am being naive. I am not able get it work. I have dragged and dropped(checked copy)the libMyfiles.a to my other xcode proj.
(I haven’t added the entire framework.xcodeproj ,just added the libMyfiles.a file alone from build->Debug-universal->libMyfiles.a).When i try to import the file which is inside the static library, i get no such file exist error.Is it possible to see the headers available inside the libMyfiles.a?. Could you please guide me. Thanks.
Ah, the post assumes you’re familiar with using C libraries, sorry.
Headers are *never* included inside a library – you have to export those separately. Xcode can do this, but Apple disabled/removed the main way of doing it for iPhone, and provided no replacement (no-one really knows why).
Here’s a walkthrough for a nice way of packaging up the files in a fashion that Xcode likes:
http://bit.ly/fPEHgw
I always get the message
** BUILD SUCCEEDED **
…
lipo: for current configuration (Debug) creating output file: /Users/…./lib…a
lipo: can’t open input file: /Users/…./lib…a (no such file or directory)
There is only an empty folder in my build directory (Debug-universal).
could you help me with that?
thanks!!
Sounds like you’re missing a build-directory. Xcode4 has several bugs in this area. Check your hard-disk – does that directory exist? can you create it?
Check your Xcode4 preferences, it’s easy to set them in ways that Apple didn’t properly test / fix.
Edit the script and turn-on debug messages, see what’s happening while it runs – you should have more than enough info to see what the precise problem is.
Hello,
Thanks for the great tutorial.
I used your script and though the lib build fails with the error:
“cp: /Users/…/Build/Products/Release-iphoneos/usr/local/include/*: No such file or directory”
you script prints “** BUILD SUCCEEDED **”
and i can find the lib and also use it successfully
(for both emulator and device)
Don’t know why xcode says it failed but it works form me.
Thanks
Daniela
Great stuff!
Only two things:
* if you have various projects in the same directory (as I) the script fails but it is easy to fix it just add ‘ -project “${PROJECT_FILE_PATH}” ‘ to the xcodebuild line.
* And maybe you could have more libs in the ‘universal directory’, please change these two lines to prevent to erase other files:
mkdir -p “${CREATING_UNIVERSAL_DIR}”
rm -rf “${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}”
Thank you very much!
Hey…. thanks…
you have an typo in the script on the xcodebuild line extra (double quote just before ${PROJECT_FILE_PATH}
second, if you have more than one xcode installed, you might run into trouble… so you want to add something like:
DEVROOT=`xcode-select -print-path`
XCODEBUILD=”${DEVROOT}/usr/bin/xcodebuild”
I’m not sure but could be the same deal for “lipo”.
eric
Thanks – very busy getting an app launched right now, but I’ll come back and test those changes soon.
(off the top of my head – looks correct, I’d recommend people use them – but I’d want to carefully test before changing the master copy)
Did you found some time to fix the script? I tried to build a static lib with the latest Xcode but it fails all the time.
Works fine for me at the moemnt. What needs fixing?
Great work, Adam, cheers!
I’ve created an Xcode 4 template based on your script: https://github.com/michaeltyson/iOS-Universal-Library-Template
hi when i executed above script, it worked fine i found i686-apple-darwin10-llvm-g++.1 file, but where can i find the .a file?