Skip to content

Instantly share code, notes, and snippets.

@adamgit
Last active December 11, 2023 16:27
Star You must be signed in to star a gist
Save adamgit/3705459 to your computer and use it in GitHub Desktop.
Automatically create cross-platform (simulator + device) static libraries for Objective C / iPhone / iPad
##########################################
#
# c.f. http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4
#
# Version 2.82
#
# Latest Change:
# - MORE tweaks to get the iOS 10+ and 9- working
# - Support iOS 10+
# - Corrected typo for iOS 1-10+ (thanks @stuikomma)
#
# Purpose:
# Automatically create a Universal static library for iPhone + iPad + iPhone Simulator from within XCode
#
# 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)
#
set -e
set -o pipefail
#################[ 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 '\d\{1,2\}\.\d\{1,2\}$')
# 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}\" -project \"${PROJECT_NAME}.xcodeproj\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\"
xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"
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!
rm -rf "${CREATING_UNIVERSAL_DIR}"
mkdir "${CREATING_UNIVERSAL_DIR}"
#
echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"
xcrun -sdk iphoneos 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)
#
echo "Fetching headers from ${PUBLIC_HEADERS_FOLDER_PATH}"
echo " (if you embed your library project in another project, you will need to add"
echo " a "User Search Headers" build setting of: (NB INCLUDE THE DOUBLE QUOTES BELOW!)"
echo ' "$(TARGET_BUILD_DIR)/usr/local/include/"'
if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ]
then
mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
# * needs to be outside the double quotes?
cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
fi
fi
@adamgit
Copy link
Author

adamgit commented Sep 12, 2012

@fwal
Copy link

fwal commented Oct 10, 2012

Hi!

I have an updated version that checks the build settings for the location of the public headers. If you want you could merge changes in https://gist.github.com/3864958

Cheers!
/Frederik

@wookiee
Copy link

wookiee commented Nov 12, 2012

@adamgit have you considered converting this to a proper git repo, to benefit from add'l collaboration features such as pull requests?

@adamgit
Copy link
Author

adamgit commented Jan 2, 2013

PLEASE NOTE:

Github DOES NOT SEND NOTIFICATIONS when you comment here. I have to manually come to gist - not even the ghithub main site! - to "discover" you have commented.

PLEASE DO NOT COMMENT HERE unless you are willing to wait 6-12 months for a reply.

@adamgit
Copy link
Author

adamgit commented Jan 2, 2013

@theDukeW - nice changes, I'll merge them and add to StackOverflow page.

@wookiee - it's tricky ... gist is designed for this use-case, but the folks at github are taking their sweet time adding the core features that we reasonably expect. The problem with a full repository is that it makes it significantly harder for casual visitors to simply "click, select, copy, paste" - and I know that many of the people using this today like it specifically because its so effortless to setup.

Obviously, if you feel there's value in a git repo, you're free to make one, but I keep hoping Apple will Just Fix The Damn Bug already, and soon we can all stop using it :)

@yenrab
Copy link

yenrab commented Jan 5, 2013

Here is a two line change that doesn't delete the output directory but does delete the .a files. This change is very useful when building several libraries at once. Replace the mkdir and rm -rf lines for the universal dir.

[ -d "${CREATING_UNIVERSAL_DIR}" ] || mkdir "${CREATING_UNIVERSAL_DIR}"
[ ! -f "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" ] || rm "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"

@lolgear
Copy link

lolgear commented Jun 10, 2013

please, see my variant of this script in Perl:
https://gist.github.com/lolgear/5750010 ;)

@moduscreate
Copy link

Thank you for writing this. It's worked out perfectly :)

@tbowers
Copy link

tbowers commented Mar 31, 2016

My usage of this script seems to rely on my project having Build Active Architecture Only set to No. Conceptually I see why but Xcode seems to hate you having that setting to anything other than Yes, warning me incessantly. Is there a way to modify this script to override that setting?

Update
I just realized one solution - since the default is to use Release mode for command one builds, I'm simply executing this script (which is wrapped in a Target w/ other dependencies) from the command line.

@WagawinBB
Copy link

I had a problem with building the fatlib with iphoneos10.0 because the regular expression in the script only expects 9.x and lower and returns 0.0 for ios 10.0

to fix this just replace

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.{3}$')
with

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '[.0-9]{3,4}$')

@adamgit
Copy link
Author

adamgit commented Oct 5, 2016

@WagawinBB - updated with corrected grep that should work with all versions of iphoneos - we went through a couple of attempts, but latest is working for me and seems to work for others who had funky setups too.

@triplef
Copy link

triplef commented Sep 28, 2020

Xcode 12 also requires passing the architecture to xcodebuild using the -arch param. Here’s a fork that does this:
https://gist.github.com/ShaoJenChen/8ae9728939061ed4a3d4a63d01f7d404

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment