Skip to content

Instantly share code, notes, and snippets.

@atereshkov
Last active April 29, 2023 16:11
Show Gist options
  • Save atereshkov/df107336a076d5f34bc4b2a9eb6f9ad5 to your computer and use it in GitHub Desktop.
Save atereshkov/df107336a076d5f34bc4b2a9eb6f9ad5 to your computer and use it in GitHub Desktop.
Xcode 10 Fat framework - How to create universal binary iOS framework (+ Cocoapods podspec)
If you've got something like this:
file was built for arm64 which is not the architecture being linked (x86_64):
Than this gist is for you.
Let's say you have already developed the framework itself and just want to build the binary
and distribute it through the Cocoapods.
1. Make sure your framework "Deployment Target" is equal to all the dependencies from your podspec file.
2. In your framework target -> "Build Settings" -> "Skip install" set to No
3. In build schemes drop down choose your framework -> "Edit scheme" -> "Archive" -> "Post-actions" ->
Click "+" sign
4. From the "Provide build settings from" drop down choose your target.
5. Paste the script below:
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}"
#mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework"
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}/" "${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}"
echo "Combining executables end"
# Step 4. Create universal binaries for embedded frameworks
for SUB_FRAMEWORK in $( ls "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks" ); do
BINARY_NAME="${SUB_FRAMEWORK%.*}"
echo "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"
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
6. Tick "Shared" checkbox at the very bottom.
7. !!! Select real device from the devices drop down (let's say your iPhone). Run "Product" -> "Archive"
8. Wait for the Organaizer window, then wait for a while and if everything is fine
the project root will be opened in the finder.
9. You'll have MyKit.framework file in the project root folder.
10. You can open MyKit.framework, navigate to "Modules" -> "MyKit.swiftmodule" and make sure you have all the
architectures (In my case for deployment target 10 there are 4 architectures: arm, arm64, i386, x86_64)
======
In order to make the pod availiable through the Cocoapods, make the default pod setup as for usual pods.
Then make sure you have copied the .framework file to the (let's say) "Framework" folder.
You'll have .git folder, "Framework" folder, "LICENSE" file, "MyKit.podspec" files in the pod folder.
Your podspec file should looks similar to this:
Pod::Spec.new do |s|
s.name = "MyKit"
s.version = "1.0.0"
s.summary = "MyKit for iOS"
s.description = <<-DESC
MyKit for iOS.
DESC
s.homepage = "https://github.com/atereshkov/mykit"
s.license = "MIT"
s.author = "Alexander Tereshkov"
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/atereshkov/mykit.git", :tag => "#{s.version}" }
s.frameworks = 'UIKit', 'WebKit'
s.dependency 'Alamofire', '~> 4.8'
s.ios.vendored_frameworks = "Framework/MyKit.framework"
s.swift_version = "4.2"
end
======
Then run "pod lib init" from the pod root folder
-> MyKit (1.0.0)
- NOTE | xcodebuild: note: Using new build system
- NOTE | [iOS] xcodebuild: note: Planning build
- NOTE | [iOS] xcodebuild: note: Constructing build description
- NOTE | [iOS] xcodebuild: warning: Skipping code signing because the target does not have an Info.plist file. (in target 'App')
And if you got the "MyKit passed validation." phrase, then everything is fine.
Just relax and continue with the default procedure of pod publication through the Cocoapods.
https://guides.cocoapods.org/making/making-a-cocoapod.html
Pod::Spec.new do |s|
s.name = "MyKit"
s.version = "1.0.0"
s.summary = "MyKit for iOS"
s.description = <<-DESC
MyKit for iOS.
DESC
s.homepage = "https://github.com/atereshkov/mykit"
s.license = "MIT"
s.author = "Alexander Tereshkov"
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/atereshkov/mykit.git", :tag => "#{s.version}" }
s.frameworks = 'UIKit', 'WebKit'
s.dependency 'Alamofire', '~> 4.8'
s.ios.vendored_frameworks = "Framework/MyKit.framework"
s.swift_version = "4.2"
end
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}"
#mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework"
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}/" "${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}"
echo "Combining executables end"
# Step 4. Create universal binaries for embedded frameworks
for SUB_FRAMEWORK in $( ls "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Frameworks" ); do
BINARY_NAME="${SUB_FRAMEWORK%.*}"
echo "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${TARGET_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"
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
@fatmaselin
Copy link

Thanks for your script, it really saved me a lot of headaches!

@ParhamHatan
Copy link

ParhamHatan commented Oct 12, 2019

Hi,
Thanks for your script
In xcode 11 I changed "iPhone XS" with "iPhone 6" for simulator support, can you check it?

@atereshkov
Copy link
Author

atereshkov commented Oct 12, 2019

I suppose there's no difference between simulators. Have you came across an error using iPhone XS?

@ParhamHatan
Copy link

I suppose there's no difference between simulators. Have you came across an error using iPhone XS?

In iPhone XS only device modules added in framework
when i switched to iPhone 6, simulator modules added to framework
also tried iPhone X nothing happened like iPhone XS

@simform-solutions
Copy link

simform-solutions commented Nov 18, 2019

#!/bin/sh
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
OUTPUT_FOLDER=${PROJECT_DIR}

make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

Step 1. Build Device and Simulator versions. This also includes the full bitcode generation of the framework
xcodebuild BITCODE_GENERATION_MODE=bitcode OTHER_CFLAGS="-fembed-bitcode" -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO clean build

xcodebuild BITCODE_GENERATION_MODE=bitcode OTHER_CFLAGS="-fembed-bitcode" -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" -UseModernBuildSystem=NO clean build

Step 2. Copy the framework structure (from iPhones build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"

Step 3. Copy Swift modules (from iphonesimulator build) to the copied framework directory
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"

Step 4. Create a universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"

Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${OUTPUT_FOLDER}"
Step 6. Convenience step to open the project's directory in Finder
open "${OUTPUT_FOLDER}"

I'm running this script to build a framework with cocoapods earlier in Xcode 10.3 it's working fine, But in new Xcode 11.2.1, this script is not working. can you please find me a workaround.

error ->

error: no such file or directory: '/Users/username/Documents/GitHub/sdkname/GenaratefremeWork/Pods/@/Users/username/Library/Developer/Xcode/DerivedData/SDK-ddyylnfmgscnezeppcqlekfbplzy/Build/Intermediates.noindex/SDK.build/Debug-iphoneos/SDK.build/Objects-normal/arm64/SDK.SwiftFileList'
Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1

I tried the above script with post actions but no luck not working with Xcode 11.2.1, can you please provide the script that works in the latest Xcode.

@mapo80
Copy link

mapo80 commented Mar 6, 2021

This is the right version if product_name differs from target_name:

exec > /tmp/${PROJECT_NAME}_archive.log 2>&1

UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

echo ${UNIVERSAL_OUTPUTFOLDER}

if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Detected, stopping"
else
export ALREADYINVOKED="true"

# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
#mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework" 

echo "Building for iPhoneSimulator"
xcodebuild -workspace "${WORKSPACE_PATH}" -scheme "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -destination "generic/platform=iOS Simulator" ONLY_ACTIVE_ARCH=NO ARCHS='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}/" "${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/${PRODUCT_NAME}.framework/Modules/${PRODUCT_NAME}.swiftmodule/."

echo "Simulator dir  ${SIMULATOR_SWIFT_MODULES_DIR}"

if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PRODUCT_NAME}.framework/Modules/${PRODUCT_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}"

echo "Combining executables end"

# Step 4. Create universal binaries for embedded frameworks
for SUB_FRAMEWORK in $( ls "${UNIVERSAL_OUTPUTFOLDER}/${PRODUCT_NAME}.framework/Frameworks" ); do
BINARY_NAME="${SUB_FRAMEWORK%.*}"
echo "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${PRODUCT_NAME}.framework/Frameworks/${SUB_FRAMEWORK}/${BINARY_NAME}"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PRODUCT_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

@SuhasAithal53
Copy link

SuhasAithal53 commented Apr 1, 2021

hey thanks for your script, but I am having trouble using it with my framework which has a dependency. like I can not go to my framework project and click archive as the dependency is not added here in my project but marked as s.dependancy in the pod spec and thus client has to add it. so archive fails. can you help me with shipping the fat framework through the cocoa pod with dependencies? thanks in advance for your kind reply. basically, the dependency I added was app auth and it was built in objective c. and i couldn't find fat library for it and i added only the device framework, so once the archive has done i could see only arm64 related modules but not simulator one.

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