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
@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