Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This run script will build the iphoneos and iphonesimulator schemes and then combine them into a single framework using the lipo tool (including all the Swift module architectures). This version works with Cocoapods, merging simulator and device architectures for intermediate libraries as well. To use this script, go to Product > Scheme > Edit S…
# This XCode scheme archive post-action script combines the device and iOS simulator frameworks into a single binary
# so that the encapsulated library can be run in both contexts. PLEASE NOTE: the iOS Simulator framework must be REMOVED
# before submitting this binary to the app store
# Source: https://gist.github.com/voxels/0ec095d567bcb0bc30a7c80122b17396
# Based on: https://gist.github.com/atsepkov/1673c2d899470270e3eb313912aafc6f
# Resources:
# - https://developer.apple.com/library/content/technotes/tn2215/_index.html
# - https://pewpewthespells.com/blog/buildsettings.html
# WARNING:
# In order to submit this combined framework to the app store, an additional build script must be inserted
# into the adopting XCode project so that the iOS simulator binary is removed
# An example of this additional build script is located at: https://gist.github.com/voxels/2ab56e3bcda4a9a70053751f9d7ed5fa
# This script copies the unmodified iphoneos build product into ${SRCROOT}/archive/iphoneos/latest directory
BUILD_NUMBER=$(date +%Y%m%d%H%M)
exec > /tmp/${PROJECT_NAME}_post_archive_$BUILD_NUMBER.log 2>&1
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Detected, stopping"
else
export ALREADYINVOKED="true"
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
echo "Building for iPhoneSimulator"
xcodebuild -workspace "${WORKSPACE_PATH}" -scheme "${SCHEME_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6' ONLY_ACTIVE_ARCH=NO ARCHS='arm7 arm64 x86_64' BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" ENABLE_BITCODE=YES OTHER_CFLAGS="-fembed-bitcode" BITCODE_GENERATION_MODE=bitcode clean build
# Step 1. Copy the framework structure (from iphoneos build) to the universal folder
echo "Copying to output folder"
cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/"
mkdir -p "${SRCROOT}/archive/iphoneos/latest/${FULL_PRODUCT_NAME}"
cp -Rf "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME}" "${SRCROOT}/archive/iphoneos/latest/${FULL_PRODUCT_NAME}"
# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi
# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory
echo "Combining executables"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${EXECUTABLE_PATH}"
# Step 4. Create universal binaries for embedded frameworks
for SUB_FRAMEWORK in $( ls "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Frameworks" ); do
BINARY_NAME="${SUB_FRAMEWORK%.*}"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SUB_FRAMEWORK}/${BINARY_NAME}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${PROJECT_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"
done
# Step 5. Convenience step to copy the framework to the project's directory
echo "Copying from: ${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}"
echo "Copying to project dir: ${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}"
yes | cp -Rf "${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}"
echo "Copying to archive dir: ${SRCROOT}/archive/universal/latest/${FULL_PRODUCT_NAME}"
mkdir -p "${SRCROOT}/archive/universal/latest/${FULL_PRODUCT_NAME}"
yes | cp -Rf "${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}" "${SRCROOT}/archive/latest/${FULL_PRODUCT_NAME}"
fi
@voxels

This comment has been minimized.

Copy link
Owner Author

voxels commented May 30, 2018

Thanks to atsepkov


To use this gist in Xcode for your project, do the following:

Select the framework you're interested in building
- Go to Product > Scheme (make sure correct scheme is selected) > Edit Scheme
- Go to Archive > Post-actions and add (+) a new Run Script stage
- Make sure shell type is set to /bin/sh and copy the above script into the text area
- Change the drop down to "Provide build settings" from the project target
- Share the scheme
- To build, go to Product > Archive and you should see the organizer window after the build finishes, your regular binary will now become a fat binary.
- Set "Skip Install" build setting to NO
@voxels

This comment has been minimized.

@voxels

This comment has been minimized.

Copy link
Owner Author

voxels commented May 30, 2018

exec > /tmp/${PROJECT_NAME}_post_archive_$BUILD_NUMBER.log 2>&1
This line sets the output for the build steps logs

@voxels

This comment has been minimized.

Copy link
Owner Author

voxels commented May 30, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.