Fat Framework Script for Xcode 10?

I used this script in the post-action of the archive in my scheme to make a FAT binary framework. One that will work on the simulator and actual device.


`https://gist.github.com/gauravkeshre/eabb2a13ef6d673fadec84ca60b56b05`


Does anyone know how to convert it to work with Xcode 10?


Here is the script itself:



exec > /tmp/${PROJECT_NAME}_archive.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 "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone XS' ONLY_ACTIVE_ARCH=NO ARCHS='i386 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}/"



# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory

SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/."

if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then

cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_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}/${TARGET_NAME}.framework/Frameworks" ); do

# BINARY_NAME="${SUB_FRAMEWORK%.*}"

# lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${SUB_FRAMEWORK}/${BINARY_NAME}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"

# done



# Step 5. Convenience step to copy the framework to the project's directory

echo "Copying to project dir"

yes | cp -Rf "${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}" "${PROJECT_DIR}"



open "${PROJECT_DIR}"



fi

Replies

One that will work on the simulator and actual device.

There’s no supported way to do this. The problem is that from Xcode’s perspective iOS and the iOS simulator are two different platforms [1], and all of the slices of a universal framework must be for the same platform. If you do manage to create a framework with different platforms in its slices, you’re likely to encounter weird errors down the pike.

It would be great if you could file an enhancement request for Xcode to support this properly. We do actually have a bug on file about this already (r. 17611279), but a fresh bug report will allow you to express your needs in your own terms, and allow iOS engineering to gauge the level of demand.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] Consider this:

$ ls -l /Applications/Xcode.app/Contents/Developer/Platforms/
total 0
…
drwxr-xr-x  10 quinn  staff  320 11 Sep 23:43 iPhoneOS.platform
drwxr-xr-x   7 quinn  staff  224 11 Sep 23:43 iPhoneSimulator.platform

Also, imagine what would happen if Apple made an iOS device that used a 64-bit Intel CPU (and no, this isn’t a product announcement, it’s just a thought experiment!). Now your framework would need three slices (64-bit Arm, 64-bit Intel for device, and 64-bit Intel for simulator) and there’d be no way to tell the last two apart.

The script I presented does work with Xcode 9. Why not in Xcode 10?

The script I presented does work with Xcode 9. Why not in Xcode 10?

To be clear, this technique has never been supported. I’m not sure why it’s broken on Xcode 10, but that’s kinda the nature of unsupported things. For supported things Apple tries to maintain compatibility from release to release — and where we fail that’s worth a bug report — but we make no such promises for unsupported things.

If you want to ship a pre-built framework that supports iOS and iOS Simulator, your only supported option right now is to ship two separate frameworks. The iOS framework should contain all the architectures you support on iOS (typically 64-bit Arm) and the iOS Simulator framework should contain all the architectures you support on the iOS Simulator (typically 64-bit Intel). Your clients will then have to link with the correct framework based on their build target.

And yes, I realise that this is a suboptimal experience, which is why I recommend that you file an enhancement request for Xcode to provide a better option.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

"Your clients will then have to link with the correct framework based on their build target"


There lies the rub.

If you can show me how to do that, then I wouldn't need a "fat" binary.


Thanks in advance


-bilm

Hello eskimo,


we have similar issue. I am in the side of the client now, my SDK provider gave me an sdk for iOS devices and other iOS simulator. This adds extra complexity in my project for obvious reasons.


Some SDKs (e.g. https://www.mapbox.com/help/ios-framework-size/#cpu-architectures) write the below, Is this true?


"Our framework includes a “fat” (multi-architecture) binary that contains slices for

armv7
,
arm64
,
i386
, and
x86_64
CPU architectures. ARM slices are used by physical iOS devices, while
i386
and
x86_64
are used by Simulator and are stripped from your app during the build and archive process. When a user downloads your app from the App Store, they receive only the architecture that their device requires."


Furthermore as Bilm said (+1), would be great to have a suggestion on how to handle this without merging both (with lipo for instance)


Thanks,


Eduardo

Some SDKs (…) write the below, Is this true?

Well, it’s true that they write that (-: Seriously though, such a configuration is not supported by Apple. As I mentioned back on 11 Oct, all slices of a framework must be for the same platform, and iOS and the iOS Simulator are different platforms.

I also want to stress that I completely agree that Apple should do a better job of supporting this. I’m not aware of any good solution to this problem. All of the available options have significant drawbacks. So, once all is said and done, and you’ve decided on an approach that works for you, I encourage you to file an enhancement request describing the problems you encountered and requesting a better solution.

Furthermore as Bilm said (+1), would be great to have a suggestion on how to handle this without merging both

Agreed. Unfortunately I don’t have a good answer for you on that front. There’s three parts to this problem:

  1. Compiling

  2. Linking

  3. Embedding

You can solve most of the compiling and linking problems via the Frameworks Search Paths (

FRAMEWORK_SEARCH_PATHS
) build setting. The trick is to embed the platform name into the build setting value, for example,
ThirdPartyFrameworks/$(PLATFORM_NAME) $(inherited)
. This will cause it to search
ThirdPartyFrameworks/iphoneos
for device builds and
ThirdPartyFrameworks/iphonesimulator
for simulator builds.

One other linker gotcha relates to the Runpath Search Paths (

LD_RUNPATH_SEARCH_PATHS
) build setting. This should be set to
$(inherited) @executable_path/Frameworks
. Xcode sets this for you when you embed a framework that you’ve built from source, but you may need to set it yourself in this case.

The nasty gotcha relates to embedding. The standard technique for embedding a framework, using a Copy Files build phase, triggers ‘magic’ when you give it a framework that comes from an SDK, and I haven’t found any way to replicate that magic for non-SDK frameworks. After monkeying around with this for a while I eventually came up with a couple of solutions that work, but neither of them is clean enough that I’m happy to share them here.

At this point I’m going to have to admit defeat. I don’t support tools for DTS (my focus is on low-level APIs, like networking), so I’m not up-to-date with all the latest developments on this front. If you need help with this then I recommend that you open a DTS tech support incident and talk to one of our tools specialists.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Just incase if people wants to use an all in one a build script that will create simulator and device frameworks based on the selected schema.

Here it is:

#  This is a modified script and it will ask the target to build separate libraries for simulator and device etc 
# On project folder it will be found under Generated-Frameworks folder
# As per the documentations for XCode 10 Apple does not allow building FAT librares
# Go ahead and check https://forums.developer.apple.com/thread/66978 and https://forums.developer.apple.com/thread/109583
# Enjoy NicoX :)

set -e
# If we're already inside this script then die
if [ -n "$MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
  exit 0
fi
  export MULTIPLATFORM_BUILD_IN_PROGRESS=1

############################################
# Options
############################################
REVEAL_ARCHIVE_IN_FINDER=true
OUTPUT_DIR_NAME="Generated-Frameworks"
FRAMEWORK_NAME="${PROJECT_NAME}"

OUTPUT_DIR="${PROJECT_DIR}/${OUTPUT_DIR_NAME}/${FRAMEWORK_NAME}-${CONFIGURATION}-framework/"
SIMULATOR_LIBRARY_OUT_DIR="${OUTPUT_DIR}/Simulator/"
GENERATED_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal"
SIMULATOR_FRAMEWORK_OUT_DIR="${GENERATED_LIBRARY_DIR}/Simulator"
FRAMEWORK="${SIMULATOR_FRAMEWORK_OUT_DIR}/${FRAMEWORK_NAME}.framework"

########################################################################
# Build Frameworks
########################################################################
xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator OBJROOT="${OBJROOT}/DependantBuilds" 

########################################################################
# Create directory for general
########################################################################
rm -rf "${GENERATED_LIBRARY_DIR}"
mkdir "${GENERATED_LIBRARY_DIR}"
mkdir "${SIMULATOR_FRAMEWORK_OUT_DIR}"
mkdir "${FRAMEWORK}"

########################################################################
# Copy files Framework
########################################################################
SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework"

########################################################################
# Make a binary for simulaotr ie. x86_64 ot i386 file system
########################################################################
# For Swift framework, Swiftmodule needs to be copied in the universal framework 
if [ -d "${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
  cp -f "${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/"* "${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
cp -r "${SIMULATOR_LIBRARY_PATH}/." "${FRAMEWORK}"

########################################################################
# Copy simulator build library in output folder
########################################################################
rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
mkdir -p ${SIMULATOR_LIBRARY_OUT_DIR}

cp -r "${FRAMEWORK}" "$SIMULATOR_LIBRARY_OUT_DIR"
#########################################################################

echo "Simulator Lib Path ---->" "${SIMULATOR_LIBRARY_PATH}" "<----"

if [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then
  open "${SIMULATOR_LIBRARY_OUT_DIR}/"
fi


##########################################################################
# Now lets build for device; shall we?
##########################################################################
DEVICE_LIBRARY_OUT_DIR="${OUTPUT_DIR}/Device/"
DEVICE_FRAMEWORK_OUT_DIR="${GENERATED_LIBRARY_DIR}/Device"
FRAMEWORK="${DEVICE_FRAMEWORK_OUT_DIR}/${FRAMEWORK_NAME}.framework"

########################################################################
# Build Device Frameworks
########################################################################
xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphoneos ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos OBJROOT="${OBJROOT}/DependantBuilds" 

########################################################################
# Create directory for general
########################################################################
rm -rf "${GENERATED_LIBRARY_DIR}"
mkdir "${GENERATED_LIBRARY_DIR}"
mkdir "${DEVICE_FRAMEWORK_OUT_DIR}"
mkdir "${FRAMEWORK}"

########################################################################
# Copy files Framework
########################################################################
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework"

########################################################################
# Make a binary for device ie. arm7, arm7v, arm8 file system
########################################################################
# For Swift framework, Swiftmodule needs to be copied in the universal framework 
if [ -d "${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
  cp -f "${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/"* "${FRAMEWORK}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
cp -r "${DEVICE_LIBRARY_PATH}/." "${FRAMEWORK}"
########################################################################
# Copy device build library in output folder
########################################################################
mkdir -p ${DEVICE_LIBRARY_OUT_DIR}
cp -r "${FRAMEWORK}" "$DEVICE_LIBRARY_OUT_DIR"

echo "Device Lib Path ---->" "${DEVICE_LIBRARY_PATH}" "<----"

if [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then
  open "${DEVICE_LIBRARY_OUT_DIR}/"
fi

let kudus = "Kudos to eskimo" +"1" + "@apple.com"

log(messege: "\(kudus)"


Please feel free to update and share!!

Hi,

Can I ask that bitcode is generated when you build for simulator? It doesn't for me, I'm using the following command to verify:


otool -arch x86_64 -l MyFramework.framework/MyFramework | grep LLVM

created enhancement request 47111236. Thanks eskimo

This is on a slightly related topic, i am trying to package and publish a Swift FAT framework and it does not seem to work. I get issues with Simulator architecture not being able to find header files during compilation, but the device architecture works fine.

I had the same issue and noticed that the Xcode 10.2 release notes mention that the Swift bridging header generated in frameworks is broken. It used to be the same shared between device and simulator flavors, but now you do need to combine them together using `#if TARGET_OS_SIMULATOR`, etc.

You are right, known issue 48635615 here

https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_release_notes?language=objc

explains the problem.


I have solved my problem with the script found here

https://gist.github.com/Shehryar/0c29cce7eead9c724b4c16b018bab63f

There’s some good news on this front. Xcode 11 beta supports a new concept, XCFrameworks, that makes it possible to bundle a multi-platform binary framework into a single distributable product. See WWDC 2019 Session 416 Binary Frameworks in Swift for the details.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Here is another alternative, you can use this script for 2020 https://github.com/gurhub/universal-framework

Best.