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)
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)
Bonus script to let you auto-include Bundles (i.e. include PNG files, PLIST files etc from your library!) - see below (scroll to bottom)
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)
"copy headers" section now respects the build setting for the location of the public headers (courtesy of Frederik Wallner)
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
- Create a static lib project
- Select the Target
- In "Build Settings" tab, set "Build Active Architecture Only" to "NO" (for all items)
- In "Build Phases" tab, select "Add ... New Build Phase ... New Run Script Build Phase"
- Copy/paste the script (above) into the box
...BONUS OPTIONAL usage:
- OPTIONAL: if you have headers in your library, add them to the "Copy Headers" phase
- OPTIONAL: ...and drag/drop them from the "Project" section to the "Public" section
- 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)
- 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:
Add the following code to the very end of the script (courtesy of Frederik Wallner): open "${CREATING_UNIVERSAL_DIR}"
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"
if you're using a custom "build output" directory for XCode4, then XCode puts all your "unexpected" files in the wrong place.
- Build the project
- Click on the last icon on the right, in the top left area of Xcode4.
- Select the top item (this is your "most recent build". Apple should auto-select it, but they didn't think of that)
- 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)
- Do everything above, check it works
- Create a new Run Script phase that comes AFTER THE FIRST ONE (copy/paste the code below)
- Create a new Target in Xcode, of type "bundle"
- 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)
- 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}"
Best Answer
I'm not really disagreeing with DarkDust's answer, but if I may channel my inner Bill Clinton, it depends on what the meaning of supported is :)
Apple doesn't want you doing this for App Store apps, but the operating system certainly allows it. Jailbreak apps use this technique all the time. You basically use a standard UNIX technique to dynamically open a framework/library, and then use stuff in it. The dlopen function allows you to open the library by passing in the path to that framework, or dylib. From some docs for building jailbreak apps, here's an example of calling an
init()
function implemented inside your own, separate dylib:Furthermore, the default restriction against allowing you to build a dynamic library project for iOS is something in Xcode that you have the ability to override by editing some XCode xml files:
Build and use dylib on iOS
Once you do this, you can build a normal iOS .dylib library, and use it per the sample code above. (yes, you probably will have to unlock this capability again whenever you install a new XCode version).
So, it's not a technical limitation, but an App Store policy limitation. If you're not limited to the App Store, then you can do it. Note that this technique does not require jailbreaking, although if the app is sandboxed, it may limit where dylibs can be loaded from.
Edit: in order to make sure this information isn't lost to future link rot, here is the content of the link I provided on how to enable iOS dylibs in Xcode. (Note: this process still works on Xcode 4, but see comment(s) below for updates to paths, etc.) Source is the iOS Place blog:
Xcode does not allow you to build dylib for iOS. App will be rejected if it’s not single binary. But I have an application that has plug-in architecture to load optional modules. I just want a quick prototype to prove concept before fully port it to iOS. It’s faster to do if dylib could simply work. So, this post shows how to build and use dylib but be aware it won’t be approved to App Store. (Tested with Xcode 3.2.4 on 10.6.4)
1. Open these files in the Property List Editor: /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Product Types.xcspec and /Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications/iPhone Simulator ProductTypes.xcspec
2. Locate the item in the “MacOSX Product Types.xcspec” that has the product type
com.apple.product-type.library.dynamic
and drag it to the “iPhone Simulator ProductTypes.xcspec”.3. Open “MacOSX Package Types.xcspec” and “iPhone Simulator PackageTypes.xcspec” found at the same places.
4. Locate the item in the “MacOSX Product Types.xcspec” that has the package type
com.apple.package-type.mach-o-dylib
and drag it to the “iPhone Simulator PackageTypes.xcspec”.5. Repeat the steps for the “iPhoneOS.platform” and relaunch Xcode if it was running.
Now, lets build a dylib. Start out with the “Cocoa Touch Static Library” Template. That should included the Foundation.framework in the project. Here are the changes I made on top of the template to build dylib.
1. Open the file project.pbxproj (found inside the Xcode project file bundle) in a Text Editor. Search for string “producttype”, change it’s value to
com.apple.product-type.library.dynamic
;Now, open the project with Xcode, go to Project->Edit Project Settings
2. “Installation Directory” set to
@executable_path/
because I plan to put the dylib in the same directory as the app’s executable.3. “Mach-O Type” set to Dynamic Library
4. “Executable Extension” set to dylib
5. “Executable Prefix” set to empty
6. Add one or two simple methods to the library and build it.
Now, create an app to test it. This time, I choose the View-based Application. Hook up a UIButton and a UILabel to call the lib and showing return message. You can download the complete project TestApp and play with it.