290

It appears that we can - theoretically - build a single static library that includes both simulator and iPhone and iPad.

However, Apple has no documentation on this that I can find, and Xcode's default templates are NOT configured to do this.

I'm looking for a simple, portable, re-usable technique that can be done inside Xcode.

Some history:

  • In 2008, we used to be able to make single static-libs that included both sim and device. Apple disabled that.
  • Throughout 2009, we made pairs of static libs - one for sim, one for device. Apple has now disabled that too.

References:

  1. This is a great idea, it's an excellent approach, but it doesn't work: http://www.drobnik.com/touch/2010/04/universal-static-libraries/

    • There's some bugs in his script that means it only works on his machine - he should be using BUILT_PRODUCTS_DIR and/or BUILD_DIR instead of "guesstimating" them)
    • Apple's latest Xcode prevents you from doing what he's done - it simply will not work, due to the (Documented) change in how Xcode processes targets)
  2. Another SO questioner asked how to do it WITHOUT xcode, and with responses that focussed on the arm6 vs arm7 part - but ignored the i386 part: How do i compile a static library (fat) for armv6, armv7 and i386

    • Since Apple's latest changes, the Simulator part isn't the same as the arm6/arm7 difference any more - it's a different problem, see above)
natevw
  • 16,807
  • 8
  • 66
  • 90
Adam
  • 32,900
  • 16
  • 126
  • 153
  • 3
    @Cawas - the "weight" of the library is irrelevant in 95% of real-world situations - for most of us, the libs are tiny, especially compared to e.g. displaying even just one single UIImageView. – Adam Jan 15 '11 at 14:58
  • 1
    @Cawas - meanwhile, the value here is that you make it MUCH easier for other people to use/re-use your library. It becomes a one-stage drag/drop process. – Adam Jan 15 '11 at 14:59
  • 5
    @Cawas - finally, a surprisingly valuable benefit: it is *so easy* to accidentally send someone the "wrong" compiled library - XCode does zero checks, and will happily compile the "wrong" architecture into the named file you thought was the "correct" architecture. Apple *keeps breaking Xcode* in this area - each new version has changes that mean "the button you pressed yesterday to compile your lib correctly will today compile it incorrectly". Until Apple stops messing us all around, we need to idiot-proof their bad UI :). – Adam Jan 15 '11 at 15:01
  • Adam, so essentially the point is maintaining same behavior on simulator and iPhone? – cregox Jan 15 '11 at 18:58
  • @Cawas - yep ... with several independent benefits – Adam Jan 16 '11 at 20:29
  • 1
    That would really be great! Because right now as it is, we just can't rely on the simulator for anything little bit more complex. – cregox Jan 19 '11 at 01:11
  • Please answer this related question: [Does lipo increase final binary size?](http://stackoverflow.com/questions/22489812/does-lipo-increase-final-binary-size) – ma11hew28 Mar 18 '14 at 20:04

10 Answers10

275

ALTERNATIVES:

Easy copy/paste of latest version (but install instructions may change - see below!)

Karl's library takes much more effort to setup, but much nicer long-term solution (it converts your library into a Framework).

Use this, then tweak it to add support for Archive builds - c.f. @Frederik's comment below on the changes he's using to make this work nicely with Archive mode.


RECENT CHANGES: 1. Added support for iOS 10.x (while maintaining support for older platforms)

  1. Info on how to use this script with a project-embedded-in-another-project (although I highly recommend NOT doing that, ever - Apple has a couple of show-stopper bugs in Xcode if you embed projects inside each other, from Xcode 3.x through to Xcode 4.6.x)

  2. Bonus script to let you auto-include Bundles (i.e. include PNG files, PLIST files etc from your library!) - see below (scroll to bottom)

  3. now supports iPhone5 (using Apple's workaround to the bugs in lipo). NOTE: the install instructions have changed (I can probably simplify this by changing the script in future, but don't want to risk it now)

  4. "copy headers" section now respects the build setting for the location of the public headers (courtesy of Frederik Wallner)

  5. Added explicit setting of SYMROOT (maybe need OBJROOT to be set too?), thanks to Doug Dickinson


SCRIPT (this is what you have to copy/paste)

For usage / install instructions, see below

##########################################
#
# c.f. https://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

INSTALL INSTRUCTIONS

  1. Create a static lib project
  2. Select the Target
  3. In "Build Settings" tab, set "Build Active Architecture Only" to "NO" (for all items)
  4. In "Build Phases" tab, select "Add ... New Build Phase ... New Run Script Build Phase"
  5. Copy/paste the script (above) into the box

...BONUS OPTIONAL usage:

  1. OPTIONAL: if you have headers in your library, add them to the "Copy Headers" phase
  2. OPTIONAL: ...and drag/drop them from the "Project" section to the "Public" section
  3. OPTIONAL: ...and they will AUTOMATICALLY be exported every time you build the app, into a sub-directory of the "debug-universal" directory (they will be in usr/local/include)
  4. OPTIONAL: NOTE: if you also try to drag/drop your project into another Xcode project, this exposes a bug in Xcode 4, where it cannot create an .IPA file if you have Public Headers in your drag/dropped project. The workaround: dont' embed xcode projects (too many bugs in Apple's code!)

If you can't find the output file, here's a workaround:

  1. Add the following code to the very end of the script (courtesy of Frederik Wallner): open "${CREATING_UNIVERSAL_DIR}"

  2. Apple deletes all output after 200 lines. Select your Target, and in the Run Script Phase, you MUST untick: "Show environment variables in build log"

  3. if you're using a custom "build output" directory for XCode4, then XCode puts all your "unexpected" files in the wrong place.

    1. Build the project
    2. Click on the last icon on the right, in the top left area of Xcode4.
    3. Select the top item (this is your "most recent build". Apple should auto-select it, but they didn't think of that)
    4. in the main window, scroll to bottom. The very last line should read: lipo: for current configuration (Debug) creating output file: /Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    ...that is the location of your Universal Build.


How to include "non sourcecode" files in your project (PNG, PLIST, XML, etc)

  1. Do everything above, check it works
  2. Create a new Run Script phase that comes AFTER THE FIRST ONE (copy/paste the code below)
  3. Create a new Target in Xcode, of type "bundle"
  4. In your MAIN PROJECT, in "Build Phases", add the new bundle as something it "depends on" (top section, hit the plus button, scroll to bottom, find the ".bundle" file in your Products)
  5. In your NEW BUNDLE TARGET, in "Build Phases", add a "Copy Bundle Resources" section, and drag/drop all the PNG files etc into it

Script to auto-copy the built bundle(s) into same folder as your FAT static library:

echo "RunScript2:"
echo "Autocopying any bundles into the 'universal' output folder created by RunScript1"
CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
cp -r "${BUILT_PRODUCTS_DIR}/"*.bundle "${CREATING_UNIVERSAL_DIR}"
Adam
  • 32,900
  • 16
  • 126
  • 153
  • 2
    I've used this on a few projects now, and shipped stuff into the app-store that used this to build the libraries. All worked 100% OK, so I'm sticking with this for now (until Xcode 4, perhaps) – Adam Oct 12 '10 at 07:28
  • Adam, looks like this breaks for Xcode 4. Have you updated it by any chance? – Alasdair Allan Mar 11 '11 at 15:39
  • Ouch. Sorry. I'm not using 4 yet. I'll check + fix when xcode4 goes gold but not before - Apple has a long history of making massive changes in the final build. IIRC we're not allowed to submit to the app store using xcode4 yet? – Adam Mar 13 '11 at 16:47
  • Xcode 4 went gold as the default editor last Thursday along with iOS 4.3. I'm working on something else right now or I'd have looked at things in more detail, sorry, but it looks like the default build directories have moved into ~/Library/Developer/Xcode/DerivedData/PROJECTNAME-LONGHEXSTRING/. – Alasdair Allan Mar 14 '11 at 09:58
  • Seems to work fine for me in xCode 4 which suggests you have updated this. I just wanted to make this comment for others to know that this works great in xcode 4. to find the result. just right click the product .a file in the project explorer and click show in finder...then back up to the parent directory and you should see a folder called Debug-universal – davydotcom Mar 15 '11 at 13:32
  • Didn't work for me in Xcode4. There's AT LEAST ONE BUG in Xcode4 which breaks the script (I'm trying to find a workaround now :(). There's also a typo in the shell script that stops it from working - unless a moderator edited it, I must have accidentally introduced it when adding a recent change - I will fix that ASAP too. – Adam Mar 24 '11 at 12:47
  • Done. I think it worked for "new" projects created in Xcode 4, but failed on "old" projects imported from Xcode 3 (Apple doesn't do much/any unit testing, they ALWAYS have import bugs with xcode versions :)). Note also that the output location has changed (side-effect of fixing that bug) – Adam Mar 24 '11 at 13:17
  • I had trouble with the shell script when it got to the part about copying the include files. I changed the cp line to cp "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include/"* "${CREATING_UNIVERSAL_DIR}/usr/local/include" and it seems to be working now. I'm no shell guy but it seemed like the double-quotes were causing the script to interpret the wildcard character literally. – Jose Ibanez Mar 30 '11 at 18:59
  • Thank you so much! You save me a lot of time :) One suggestion: If you change the rm -rf "${CREATING_UNIVERSAL_DIR}" to rm -rf "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" it can be used also in a project having multiple targets (more than one libs) created in the same output directory. – Fivos Vilanakis Aug 16 '11 at 12:07
  • I attempted to use this with RestKit but was unsuccessful, any ideas? I went about doing it all manually but it'd be nice to make the script work with such projects. – Chris Wagner Nov 23 '11 at 21:24
  • Most of these comments are from last spring. Anybody get this to work with xcode4.2? It doesn't work for me. The script seems to run, but it prints out many setenv lines, and then "Showing first 200 notices only", and then shuts up. If I redirect xcodebuild output > /dev/null, then it gets all the way through the script, but can't find the .a libs it's looking for... – Colin Nov 30 '11 at 00:50
  • SETENV lines should only appear because *your* Xcode project has the "show all setenv lines" option checked in your script phase - that's not part of the script. – Adam Dec 02 '11 at 13:24
  • Re: xcode4.2 - you followed the instructions in the comments about "finding" the output file on Xcode4, right? So far, I can't find a way to fix the "feature" (bug!) in Xcode that doesn't allow you to directly add new "Product" entries. If you know how to, please let me know and I'll modify the script/instructions! – Adam Dec 02 '11 at 13:27
  • I know that this script output is for all platforms (device,simulator) but what about all configurations? I didn't understand if I should create a library for "Debug" and than another for "Release". What exactly is the role of ${CONFIGURATION} in this script, if I need to give the static library to an external developer who need it to work for both configurations? – Oded Regev Jan 29 '12 at 16:17
  • @OdedRegev - Debug/Release *makes no difference* when you include the lib in an external project. The D/R differences are internal. Unlike Platforms, a "Release" build can safely include a "Debug" library (or vice versa). It's up to you as a developer whether you want to ship a D or an R compiled lib (although generally I'd expect you to ship an R compiled one - you don't expect people to debug your library for you!) – Adam Jan 30 '12 at 18:00
  • The script may not work correctly if there are multiple projects in a single directory. You may want to add `-project` to the xcodebuild call, the name of the current project is in `${PROJECT}` (just add .xcodeproj to its end). – Mecki Mar 06 '12 at 18:33
  • Got an error "'arm/types.h' file not found" from script while building for device. This issue was treated by adding -arch "i386" parameter for xcodebuild tool (when OTHER_SDK_TO_BUILD = iphonesimulatorX.X). Also i386 architecture must be set in the "Valid architectures" build settings. ) – Orange Jun 06 '12 at 10:53
  • Follow the 4 steps under "Usage". It's very very very easy. If things don't work for you, go back and start reading all the notes. Submit bug reports to Apple if you're not happy - they're the ones that keep changing Xcode, so that I have to keep adding "if that doesn't work, try this". – Adam Jul 29 '12 at 11:54
  • Great script. Unfortunately, I've encountered an issue that I don't quite understand in that when I try to compile and run the script, I get the following "Shell Script Invocation Warning" of "warning: no rule to process file '$(PROJECT_DIR)/GDInAppStore/GDInAppStore.m' of type sourcecode.c.objc for architecture i386". I've got i386 listed in my Valid Architectures and Architectures. Any advice? – Nobosi Aug 22 '12 at 14:25
  • @Nobosi if you dont know what that means, create a new question for it – Adam Aug 23 '12 at 00:58
  • @Adam I did before I commented, I did not receive an answer and so hoped you could've provided a hint. Anyways, for those who encounter a problem similar to mine and don't have the expertise to solve it, you can use Karl's template. – Nobosi Aug 30 '12 at 14:24
  • 2
    Can anyone confirm if this method works for XCode 4.5? I am trying to compile a static library and use it in my main project. I am able to run this on the device but not on the simulator. This is the error I get: missing required architecture i386 in file /Users/alex/Documents/iphone/production/iphone/mymedia/libMyUnrar4iOS.a (2 slices) – Alex1987 Sep 17 '12 at 18:16
  • @Alex1987 I'll test + update once 4.5 is out of beta. Until then, the bugs in 4.5 are so severe I can't use it myself :(. Even though it fixes some pretty nasty bugs in 4.4 :) – Adam Sep 17 '12 at 23:12
  • @Adam I tried this with Karl's approach and it worked. Still no success using your method. – Alex1987 Sep 21 '12 at 14:12
  • @Alex1987 according to Google, it's a bug with your version of Xcode (4.5), nothing to do with this script (allegedly: your project settings are corrupt, because of the way you created the project). Karl's framework presumably works because it circumvents the bugs in YOUR project. – Adam Sep 22 '12 at 18:39
  • Thanks so much for this script!!! I'm wondering why headers being put into public aren't exported for some projects and for others they are copied to usr/local/include. Anyone any idea? – Nick Weaver Oct 23 '12 at 13:51
  • @Alex1987 - I've been using the script with Xcode 4.5 and 4.5.1 now for a few weeks, no problems, no modifications needed. My guess is that your problem was because you were using a beta, and it was a bug that Apple fixed? – Adam Oct 25 '12 at 09:35
  • @NickWeaver - I've seen that before where someone had added the "copy-headers" phase AFTER the "run-script" phase - so the headers didn't exist for the script to output them :) – Adam Oct 25 '12 at 09:36
  • Star must be outside but slash inside ;-) `"${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}/"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"` – blackjacx May 21 '13 at 11:14
  • @blackjacx do you have a case where the slash is needed? It's been working fine for me for many projects with no slash – Adam May 22 '13 at 10:05
  • If building library, you get error saying something about dependencies and can't build for i386, then add "i386" to valid architectures in Build Settings. Works like a charm :) Thanks for tutorial – Paulius Vindzigelskis Jul 15 '13 at 14:28
  • 2
    Any idea how to make this work with XCode 5 and ARM64? If I leave architectures as standard, it makes the library with armv7, armvs7 and i386 as expected. If I set architectures to standard including 64bit, then the library only contains "cputype 16777223". I use otool -h on the .a file to verify what is inside – Roger Binns Oct 23 '13 at 22:44
  • @RogerBinns I don't know yet - traditionally, Apple breaks Xcode each time they add a new architecture (they didn't test their own code when armv7s came out either :( ). So far, on all our library-heavy projects: we're ignoring ARM64, hoping Apple will ship a fix in Xcoe 5.0.1, or someone on SO.com will find a workaround soon. – Adam Oct 24 '13 at 10:39
  • 1
    XCode5 has made adding a run script build phase even more tricky. check out this: http://www.runscriptbuildphase.com/ – Fabio Napodano Nov 25 '13 at 12:01
  • either i don't understand or i can not achieve "and drag/drop them from the "Project" section to the "Public" section" in Xcode 5. – Mihir Mehta Mar 06 '14 at 11:59
  • @mihirmehta Sometimes Apple's UI code crashes internally (Xcode5 is buggy). If you can't drag/drop, you can select the .h file in your project, open the right sidebar, and one of the tabs has a dropdown for the filename set to "Project", which you can change to "Public" -- this has identical effect, but it's more annoying to do by hand. – Adam Mar 08 '14 at 14:55
  • 1
    This appears to work fine on Xcode 6 with no changes (only tried a couple of projects so far, and haven't submitted any App Store updates yet, but all are working fine so far). – Adam Oct 15 '14 at 14:39
  • Note: I've tried making frameworks with Xcode6, and Apple's new support, but it appears Apple didn't quite tell the truth: frameworks still don't work properly (1 year after launch!) and rumour has it that Apple engineers have confessed they never intended it to work. It's only meant to work inside a single project (which is useless in most cases). You STILL need to use this script to hack around Apple's bad support - if I get frameworks working, I'll do an update of the script. – Adam Aug 13 '15 at 21:20
  • 1
    XCode7: still works, but Apple implies they will permanently break this in Xode 7.1 or Xcode 8. For now, you usually have to disable the new (optional, and poorly implemented) "bit code" feature, because it has bugs and causes a conflict in other Apple behind-the-scenes tools when you merge files. It is possible to workaround this by rewriting a big chunk in the middle of the script - if anyone wants to have a go at making this bit-code compatible, fork the gist and ping me and I'll help test it. – Adam Oct 03 '15 at 12:41
  • Also: you probably should switch to using Frameworks now that Apple has finally unblocked them! It took 5 years, but hopefully this script can gradually be retired (certainly for new projects). Although its still useful as a start for anyone trying to make similar auto-build effects with custom resources and project-layouts. – Adam Jan 13 '16 at 16:36
  • @Adam I still use this in xCode 8.0 for compiling the static library & copying header files. Why do you recommend switching to Frameworks? And what do you mean by 'unblocked'? –  Jul 11 '16 at 18:02
  • 1
    @Cookies - great! But frameworks are a bit cleaner, more explicit (because Xcode has special support for showing them in the GUI), more standard-practice (more people know what they are ... than know this script), and (potentially) apple can optimize the build process. But if this works for you, great. – Adam Aug 04 '16 at 09:47
  • 1
    @Cookies - "unblocked" because frameworks always worked on iOS, but Apple put a hardcoded block in Xcode to prevent anyone using them. No reason was ever given (as far as I know). – Adam Aug 04 '16 at 09:48
  • Thanks for the update! In line 41 you missed a closing bracket " line 41: unexpected EOF while looking for matching ')'". It should be `SDK_VERSION=$(echo ${SDK_NAME} | grep -o '\d\{1,2\}\.\d\{2\}$)'` – stuikomma Sep 29 '16 at 17:08
  • Doh! Thanks @stuikomma - fixed – Adam Sep 30 '16 at 15:55
  • UPDATE: seems on some Xcode installs that we're getting 2 digits after the period, and others only 1 (might be localised issues there?). Apple refuses to publish a definition here, so I'm inferring it could be either. – Adam Oct 05 '16 at 13:00
  • @Adam First, thank you for the grate work. its work perfectly in my static library. but I have in my library another library project that should compile with my project and my project is linking binary with this library (subSdk.a is appearing in "Link Binary With Libraries") here I got error when I tried to create build for the "OTHER_SDK_TO_BUILD" it doesn't recognise my inner .a file . I tried to add "OTHER_LDFLAGS" in xcodebuild line but nothing. any idea what should I do if i have Link Binary With Libraries in my project? – Developeder Jan 02 '17 at 17:02
  • @EderYif open a new StackOverflow question; that situation is significantly different / wider issue than this :). Also I think you need to add more detail, possibly including screenshots, because it should work fine, but I think you might be getting some steps wrong. – Adam Jan 02 '17 at 22:36
  • 1
    One case this hasn’t been covered here yet is using this script for archive builds. When archiving the script will create the fat library in the "universal" directory it creates, but the archive itself will contain the original non-fat library. I fixed this by changing `CREATING_UNIVERSAL_DIR` to `${SYMROOT}/../InstallationBuildProductsLocation${INSTALL_PATH}` and removing the `rm -rf` and `mkdir` calls just below this line. You might also want to enable the option _Run script only when installing_ so that non-archive builds simply create non-fat builds. – Frederik Aug 10 '17 at 12:01
  • 1
    @Frederik nice catch! I'm not using this script for any live projects right now to test that, but it looks like a good improvement. Next time I'm testing/adding to a project, I'll check this out with all my current and old projects, and add if there's no problems, thanks! – Adam Aug 19 '17 at 23:07
  • The core function of this script works a treat with Xcode 9. The changes at the end for the headers don't seem to work though. I also failed to get the changes suggested by @Frederik to work for an archive operation. – PKCLsoft Sep 07 '18 at 07:07
  • I was able to create MyLib.a file. But I get "No such module" in my app when I add it. – Nitish Feb 25 '19 at 20:17
89

I have spent many hours trying to build a fat static library that will work on armv7, armv7s, and the simulator. Finally found a solution.

The gist is to build the two libraries (one for the device and then one for the simulator) separately, rename them to distinguish from each other, and then lipo -create them into one library.

lipo -create libPhone.a libSimulator.a -output libUniversal.a

I tried it and it works!

Stunner
  • 12,025
  • 12
  • 86
  • 145
g_low
  • 2,435
  • 19
  • 23
74

I've made an XCode 4 project template that lets you make a universal framework as easily as making a regular library.

Mohsin Khubaib Ahmed
  • 1,008
  • 16
  • 32
Karl
  • 14,434
  • 9
  • 44
  • 61
  • Couldn't build it with iOS 4.3 target. Get the following error: invalid deployment target for -stdlib=libc++ (requires iOS 5.0 or later) – Alex1987 Oct 20 '12 at 12:40
  • I wish I could give more reputation points for this answer... so much easier than using CMake to create a static library. Thank you so much for doing this! – iwasrobbed Jan 23 '13 at 20:05
  • It works also with iOS 6 for me. But maybe it is because my lib is quite simple and without any dependencies and resources – Paulius Vindzigelskis Mar 06 '13 at 09:06
  • There is a BIG problem with that solution: other who want's to use framework created by this solution (this solution suggest to install fremework template to xcode) MUST install this template to THEIR xcode!!! – evya Sep 28 '14 at 12:16
  • You only need to install the template for real frameworks. Fake frameworks will run fine in unmodified Xcode. – Karl Oct 29 '14 at 21:13
30

There is a command-line utility xcodebuild and you can run shell command within xcode. So, if you don't mind using custom script, this script may help you.

#Configurations.
#This script designed for Mac OS X command-line, so does not use Xcode build variables.
#But you can use it freely if you want.

TARGET=sns
ACTION="clean build"
FILE_NAME=libsns.a

DEVICE=iphoneos3.2
SIMULATOR=iphonesimulator3.2






#Build for all platforms/configurations.

xcodebuild -configuration Debug -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Debug -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Release -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Release -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO







#Merge all platform binaries as a fat binary for each configurations.

DEBUG_DEVICE_DIR=${SYMROOT}/Debug-iphoneos
DEBUG_SIMULATOR_DIR=${SYMROOT}/Debug-iphonesimulator
DEBUG_UNIVERSAL_DIR=${SYMROOT}/Debug-universal

RELEASE_DEVICE_DIR=${SYMROOT}/Release-iphoneos
RELEASE_SIMULATOR_DIR=${SYMROOT}/Release-iphonesimulator
RELEASE_UNIVERSAL_DIR=${SYMROOT}/Release-universal

rm -rf "${DEBUG_UNIVERSAL_DIR}"
rm -rf "${RELEASE_UNIVERSAL_DIR}"
mkdir "${DEBUG_UNIVERSAL_DIR}"
mkdir "${RELEASE_UNIVERSAL_DIR}"

lipo -create -output "${DEBUG_UNIVERSAL_DIR}/${FILE_NAME}" "${DEBUG_DEVICE_DIR}/${FILE_NAME}" "${DEBUG_SIMULATOR_DIR}/${FILE_NAME}"
lipo -create -output "${RELEASE_UNIVERSAL_DIR}/${FILE_NAME}" "${RELEASE_DEVICE_DIR}/${FILE_NAME}" "${RELEASE_SIMULATOR_DIR}/${FILE_NAME}"

Maybe looks inefficient(I'm not good at shell script), but easy to understand. I configured a new target running only this script. The script is designed for command-line but not tested in :)

The core concept is xcodebuild and lipo.

I tried many configurations within Xcode UI, but nothing worked. Because this is a kind of batch processing, so command-line design is more suitable, so Apple removed batch build feature from Xcode gradually. So I don't expect they offer UI based batch build feature in future.

eonil
  • 83,476
  • 81
  • 317
  • 516
  • Thanks, it's really interesting that the underlying *simple* commands still appear to work - it's just that Apple broke their GUI spectacularly. Looks like I could make a fully custom project template that would "not suck" and fix the things Apple broke, by pre-making all the Targets, and wiring up this script with xcode build vars. I'll try it out on my next project :) – Adam Sep 02 '10 at 20:01
  • 1
    I used a script similar to this and put it under a new target containing only the shell script. The recursive build script above is very clever, but unnecessarily confusing. – benzado Feb 09 '11 at 18:52
  • 1
    I prefer shell scripts for stuff like this, here is my take https://gist.github.com/3178578 – slf Jul 25 '12 at 20:47
  • @benzado Yeah I avoided complexity intentionally because I think shell script must be easy to read for modifying. – eonil Jul 26 '12 at 05:42
  • lipo: can't open input file: /Debug-iphoneos/ – Dima Mar 11 '13 at 09:11
  • Something to note is that as of Xcode 5, for the -sdk parameter, the values should be iphoneos, or iphonesimulator, no version, all lowercase. – aeskreis Jan 15 '14 at 20:17
11

I needed a fat static lib for JsonKit so created a static lib project in Xcode and then ran this bash script in the project directory. So long as you've configured the xcode project with "Build active configuration only" turned off, you should get all architectures in one lib.

#!/bin/bash
xcodebuild -sdk iphoneos
xcodebuild -sdk iphonesimulator
lipo -create -output libJsonKit.a build/Release-iphoneos/libJsonKit.a build/Release-iphonesimulator/libJsonKit.a
Brad Robinson
  • 44,114
  • 19
  • 59
  • 88
7

IOS 10 Update:

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\}$')
ben
  • 900
  • 9
  • 17
  • Thanks. I made similar change this morning, but used \d. This I think is the one we want (is it better or worse than yours?) ... grep -o '\d\{1,2\}\.\d\{2\}$' – Adam Sep 19 '16 at 16:23
  • I think mine is more reliable since it considers only numbers – ben Sep 19 '16 at 18:27
  • 1
    No, yours matches 1 particular way of writing digits. Given Apple's historical support for (and use of) prettified characters and text (e.g. in filenames) I'd expect your proprietary selection of a few digits to be less reliable. – Adam Sep 21 '16 at 08:10
  • 1
    okay maybe youre right. at least mine got my project also working and we are safe for the next 89 ios versions – ben Sep 21 '16 at 08:26
  • @ben solution works for me, Adam's regex '[\\.0-9]\{3,4\}$' gives error code 2 – Zee Oct 05 '16 at 08:27
4

I've made this into an Xcode 4 template, in the same vein as Karl's static framework template.

I found that building static frameworks (instead of plain static libraries) was causing random crashes with LLVM, due to an apparent linker bug - so, I guess static libraries are still useful!

Michael Tyson
  • 1,498
  • 1
  • 14
  • 23
  • Hi Michael, I have tried your static library template but I can compile for simulator but not for device, here the error: ** BUILD FAILED ** The following build commands failed: ProcessPCH /var/folders/qy/ncy6fkpn6677qt876ljrc54m0000gn/C/com.apple.Xcode.501/SharedPrecompiledHeaders/MenuBarUniversal-Prefix-gwxxzpanxyudmfgryorafazokagi/MenuBarUniversal-Prefix.pch.pth MenuBarUniversal/MenuBarUniversal-Prefix.pch normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler (1 failure) Showing first 200 notices only Command /bin/sh failed with exit code 65 – Kappe Nov 01 '12 at 10:40
3

XCode 12 update:

If you run xcodebuild without -arch param, XCode 12 will build simulator library with architecture "arm64 x86_64" as default.

Then run xcrun -sdk iphoneos lipo -create -output will conflict, because arm64 architecture exist in simulator and also device library.

I fork script from Adam git and fix it.

ShaoJen Chen
  • 690
  • 6
  • 10
  • A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](/help/deleted-answers) – Yunnosch Sep 30 '20 at 10:24
2

Great job! I hacked together something similar, but had to run it separately. Having it just be part of the build process makes it so much simpler.

One item of note. I noticed that it doesn't copy over any of the include files that you mark as public. I've adapted what I had in my script to yours and it works fairly well. Paste the following to the end of your script.

if [ -d "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include" ]
then
  mkdir -p "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include"
  cp "${CURRENTCONFIG_DEVICE_DIR}"/usr/local/include/* "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include"
fi
user503821
  • 643
  • 6
  • 12
  • 1
    OK, i've added that to the answer above. (haven't had a chance to test it yet, but looks correct to me) – Adam Dec 02 '10 at 15:02
1

I actually just wrote my own script for this purpose. It doesn't use Xcode. (It's based off a similar script in the Gambit Scheme project.)

Basically, it runs ./configure and make three times (for i386, armv7, and armv7s), and combines each of the resulting libraries into a fat lib.

Mohsin Khubaib Ahmed
  • 1,008
  • 16
  • 32
whooops
  • 935
  • 6
  • 11