I have xcode project with static lib and aggregate solution where I added Multi Platform Script at Build Phases so I can create xcframework:
SCHEME_NAME=${PROJECT_NAME}
FRAMEWORK_NAME=${PROJECT_NAME}
SIMULATOR_ARCHIVE_PATH="${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive"
DEVICE_ARCHIVE_PATH="${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive"
# Simulator xcarchieve
xcodebuild archive \
-scheme ${SCHEME_NAME} \
-archivePath ${SIMULATOR_ARCHIVE_PATH} \
-sdk iphonesimulator \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# Device xcarchieve
xcodebuild archive \
-scheme ${SCHEME_NAME} \
-archivePath ${DEVICE_ARCHIVE_PATH} \
-sdk iphoneos \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# Remove older version
rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}_${APPOXEE_SDK_VERSION}"
# Create Products && Documentation && SDK directories
mkdir -p "${HOME}/Desktop/${FRAMEWORK_NAME}_${APPOXEE_SDK_VERSION}/Documentation"
mkdir -p "${HOME}/Desktop/${FRAMEWORK_NAME}_${APPOXEE_SDK_VERSION}/SDK"
# Create xcframwork combine of all frameworks
xcodebuild -create-xcframework \
-library ${SIMULATOR_ARCHIVE_PATH}/Products/usr/local/lib/lib${FRAMEWORK_NAME}.a -headers ${SIMULATOR_ARCHIVE_PATH}/Products/usr/local/lib/${FRAMEWORK_NAME}Headers \
-library ${DEVICE_ARCHIVE_PATH}/Products/usr/local/lib/lib${FRAMEWORK_NAME}.a -headers ${DEVICE_ARCHIVE_PATH}/Products/usr/local/lib/${FRAMEWORK_NAME}Headers\
-output ${HOME}/Desktop/${FRAMEWORK_NAME}_${APPOXEE_SDK_VERSION}/SDK/${FRAMEWORK_NAME}.xcframework
which creates xcframework just fine. Inside that xcframework there is info.plist
which says that all architecture arm64, armv7 and i386, x86_64 are present at xcfremework, the same results I have when I do lipo -info
Podspec file looks like:
Pod::Spec.new do |s|
s.name = "TestSDK"
s.version = "6.0.4"
s.summary = "Test SDK enables developers to harnest the full power of Test Engage Platform on their iOS applications."
s.description = <<-DESC
Test SDK enables push notification in your iOS application, for engaging your application users and increasing retention.
DESC
s.homepage = "https://mapp.com"
s.license = { :type => "Custom", :file => "Licence.txt" }
s.author = { "Test Digital" => "https://test.com/contact-us/" }
s.source = { :git => "https://github.com/TestCloud/TestSDK.git", :tag => "6.0.4" }
s.ios.framework = 'UserNotifications'
s.platform = :ios, "10.0"
s.ios.vendored_frameworks = "SDK/TestSDK.xcframework"
s.preserve_paths = 'SDK/TestSDK.xcframework'
s.resource_bundle = { 'TestSDKResources' => 'SDK/TestSDKResources.bundle' }
s.requires_arc = true
end
and pod lib lint returns me an error:
Unable to find matching .xcframework slice in '../../../../../../../../Users/ssad.ter/Desktop/private_pod/TestSDK/SDK/TestSDK.xcframework RwarSDK library ios-arm64_armv7 ios-i386_x86_64-simulator' for the current build architectures (arm64 x86_64 i386).
but it contains those architecture as I explained.
the only way when pod lib lint went successfully is when I add to pod lib lint --configuration=Debug
But from the script you can see that Build for Distribution is set to YES and SKIP INSTALL to NO, same thing is done at build settings at XCode. And the Archive when you go to Edit scheme option at XCode is set to Release mode.
Can someone point me what may be the problem here?
Your plist shows you have only x86_64 and i386 architecture available for the simulator. Are you running the command on M1 mac? If yes you need to have arm64 arch support for the simulator as well or you can run pod lib lint under rosetta emulation like this:
arch -x86_64 pod lib lint
Related
We are developing a framework for work efficiency.
The framework has a dependency on RxSwift.
So, App has RxSwift in the form of a Dynamic Framework.
So I created XCFramework.
But if I create a framework with XCFramework, I cannot see the internal code.
I hope other departments can also see the internal code of our XCFramework.
I used the following command:
xcodebuild archive \-workspace <ProjectName>.xcworkspace \-scheme ‘<ProjectName>’ \-configuration Release \-destination 'generic/platform=iOS' \-archivePath ./build/<ProjectName>.framework-device.xcarchive \SKIP_INSTALL=NO \BUILD_LIBRARIES_FOR_DISTRIBUTION=YES
xcodebuild archive \-workspace <ProjectName>.xcworkspace \-scheme ‘<ProjectName>’ \-configuration Release \-destination 'generic/platform=iOS Simulator' \-archivePath ./build/<ProjectName>.framework-device.xcarchive \SKIP_INSTALL=NO \BUILD_LIBRARIES_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ./<ProjectName>.framework-device.xcarchive/Products/Library/Frameworks/<ProjectName>.framework \
-framework ./<ProjectName>.framework-simulator.xcarchive/Products/Library/Frameworks/<ProjectName>.framework \
-output ./<ProjectName>.xcframework
I also tried Universal Framework.
The Universal Framework was able to see internal code.
However, an error occurs when running in the simulator.
Could not build Objective-C module 'MyFramework'
Universal Framework Simulator Build Error Image
import UIKit
import RxSwift
import LgIotService
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let a = IotServiceManager()
a.useRxSwift().subscribe(onNext:{ result in
print(result)
})
}
}
I used the following command:
######################
# Options
######################
REVEAL_ARCHIVE_IN_FINDER=false
FRAMEWORK_NAME="${PROJECT_NAME}"
SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework"
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework"
UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal"
FRAMEWORK="${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework"
######################
# Build Frameworks
######################
rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator"
rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphoneos"
rm -rf "${UNIVERSAL_LIBRARY_DIR}"
######################
# Build Frameworks iphonesimulator - EXCLUDED_ARCHS="arm64"
######################
xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphonesimulator -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" EXCLUDED_ARCHS="arm64" CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator 2>&1
xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphoneos -configuration ${CONFIGURATION} OBJROOT="${OBJROOT}/DependentBuilds" CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos 2>&1
######################
# Create directory for universal
######################
mkdir "${UNIVERSAL_LIBRARY_DIR}"
mkdir "${FRAMEWORK}"
######################
# Copy files Framework
######################
cp -r "${DEVICE_LIBRARY_PATH}/." "${FRAMEWORK}"
######################
# Make an universal binary
######################
lipo "${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}" "${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}" -create -output "${FRAMEWORK}/${FRAMEWORK_NAME}" | echo
# 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
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
######################
# On Release, copy the result to release directory
######################
open "${BUILD_DIR}"
From Xcode12 or higher, I know that XCFramework is recommended.
Please let me know if there is a way to make the internal source code visible in XCFramework.
And is there any way to solve the error that occurred in Universal Framework?
I tried XCFramework and Universal Framework to make Framework
XCFramework works fine. But I cannot see the internal Source Code.
On the other hand, Universal Framework does not Build in Simulator.
Our goal is to create a framework that hides our internal code and provide SDK to our customers.
We have thought of creating XCFramework which fulfills our requirement. Umbrella framework is also suggested over the internet but mostly suggested to avoid that approach. Our Framework is dependent on some third-party libraries which we are using via Pods.
Issue: XCFramework does not compile pods framework. We got an error like "Xyz(Pod) module not found". Even if we add pods from the client-side it does not work.
Code to create XCFramework is as bellow
1) Create an archive for iOS platform
xcodebuild archive -workspace ABC.xcworkspace \
-scheme ABC \
-sdk iphoneos \
-archivePath "./archives/ios_devices.xcarchive" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO
2) Create an archive for iOS-Simulator platform
xcodebuild archive -workspace ABC.xcworkspace \
-scheme ABC \
-sdk iphonesimulator \
-archivePath "./archives/ios_simulators.xcarchive" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO
3) Create an XCFramework from Archives
xcodebuild -create-xcframework \
-framework ./archives/ios_devices.xcarchive/Products/Library/Frameworks/ABC.framework \
-framework ./archives/ios_simulators.xcarchive/Products/Library/Frameworks/ABC.framework \
-output build/ABC.xcframework
We got ABC XCFramework successfully but dependencies are not included in XCFramework. Any solution for this? or Is there any way where we can set framework search path to client-side? or Any alternate approach?
You can create a pod and publish it.
Check https://guides.cocoapods.org/making/making-a-cocoapod.html
Sample Podspec file with XCFramework + Third party dependency
Pod::Spec.new do |s|
s.name = 'XCFrameworkTest' # Name for your pod
s.version = '0.0.1'
s.summary = 'Sample Spec'
s.homepage = 'https://www.google.com'
s.author = { 'Sample' => 'sample#sample.com' }
s.license = { :type => "MIT", :text => "MIT License" }
s.platform = :ios
# change the source location
s.source = { :http => 'http://localhost:8080/XCFrameworkTest.zip' }
s.ios.deployment_target = '10.0'
s.ios.vendored_frameworks = 'XCFrameworkTest.xcframework' # Your XCFramework
s.dependency 'PromisesSwift', '1.2.8' # Third Party Dependency
end
After you publish your pod, Customer can use cocopods to get our framework.
In Customer's Podfile
pod 'XCFrameworkTest' #Your pod name
I have created a template for this purpose. You can test it by running the command
pod lib create YourLibName --template-url="https://github.com/zalazara/pod-template-xcframework.git"
The template basically generates an example project together with its podfile file where, in turn, the framework to be developed is embedded, then the generation file compiles the framework using the workspace.
BUILD_DIR="Build"
TMP_DIR="${BUILD_DIR}/Tmp"
IOS_ARCHIVE_PATH="${TMP_DIR}/iOS.xcarchive"
IOS_SIM_ARCHIVE_PATH="${TMP_DIR}/iOSSimulator.xcarchive"
rm -rf ${BUILD_DIR}
rm -rf "${FRAMEWORK_NAME}.xcframework"
xcodebuild archive \
-workspace "Example/${WORKSPACE}" \
-scheme ${SCHEME} \
-archivePath ${IOS_SIM_ARCHIVE_PATH} \
-sdk iphonesimulator \
SKIP_INSTALL=NO \
| xcpretty
xcodebuild archive \
-workspace "Example/${WORKSPACE}" \
-scheme ${SCHEME} \
-archivePath ${IOS_ARCHIVE_PATH} \
-sdk iphoneos \
SKIP_INSTALL=NO \
| xcpretty
xcodebuild -create-xcframework \
-framework ${IOS_SIM_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \
-framework ${IOS_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \
-output ${FRAMEWORK_NAME}.xcframework \
| xcpretty
For more information : https://github.com/zalazara/pod-template-xcframework.git
Ayyanar's solution do the trick for me, but get an error on build: "Failed to build module from its module interface; it may have been damaged or it may have triggered a bug in the Swift compiler when it was produced".
I solve it linking the target to the .xcframwork from Pod.
Hope it helps somebody.
I'm developing a small Swift framework that depends on Alamofire. I'm using it as an embedded framework of an app belonging to the same workspace and it works perfectly.
The problem arises when I want to build an universal framework with an aggregate target. Then, when executing the script to generate the framework it fails with the message No such module 'Alamofire', referring to an import Alamofire in one of my source files.
This is my Podfile:
platform :ios, '9.0'
use_frameworks!
inhibit_all_warnings!
target 'FSIBackend' do
pod 'SwiftLint'
pod 'Alamofire'
pod 'SwiftyJSON'
end
This is the script to generate the framework. It works with other frameworks without Pods dependencies so I assume that is ok:
set -e
# Setup
FRAMEWORK_NAME="${1}"
BUILD_DIR="${SRCROOT}/build"
OUTPUT_DIR="${HOME}/Desktop/"
OUTPUT="${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework"
rm -rf "${BUILD_DIR}"
rm -rf "${OUTPUT}"
mkdir -p "${OUTPUT_DIR}"
# Build
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"
# Copy the device version of framework to output.
cp -r "${BUILD_DIR}/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${OUTPUT}"
# Replace the framework executable within the framework with a new version created by merging the device and simulator frameworks' executables with lipo.
lipo -create -output "${OUTPUT}/${FRAMEWORK_NAME}" "${BUILD_DIR}/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${BUILD_DIR}/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
# Copy the Swift module mappings for the simulator into the framework. The device mappings already exist from step 6.
cp -r "${BUILD_DIR}/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${OUTPUT}/Modules/${FRAMEWORK_NAME}.swiftmodule"
# Delete build.
rm -rf "${BUILD_DIR}"
The question is that I don't know how to build my framework depending on Alamofire. Do I have to create a podspec for my framework and use it via CocoaPods? This is the first time I create a universal framework depending on a pod so I don't know if I'm doing something impossible.
Thank you so much.
Finally I could accomplish it taking into account the advice given from #mag_zbc, thank you.
I had to modify the framework generation this way:
set -e
# Setup
WORKSPACE="${1}"
FRAMEWORK_NAME="${2}"
BUILD_DIR="${SRCROOT}/build"
OUTPUT_DIR="${HOME}/Desktop/"
OUTPUT="${OUTPUT_DIR}/${FRAMEWORK_NAME}.framework"
CONFIGURATION="${CONFIGURATION}"
rm -rf "${BUILD_DIR}"
rm -rf "${OUTPUT}"
mkdir -p "${OUTPUT_DIR}"
# Build the framework for device and for simulator (using all needed architectures).
xcodebuild -workspace "${WORKSPACE}" -scheme "${FRAMEWORK_NAME}" -configuration ${CONFIGURATION} -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator" clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator
xcodebuild -workspace "${WORKSPACE}" -scheme "${FRAMEWORK_NAME}" -configuration ${CONFIGURATION} -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos" clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos
# Copy the device version of framework to output.
cp -r "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework" "${OUTPUT}"
# Replace the framework executable within the framework with a new version created by merging the device and simulator frameworks' executables with lipo.
lipo -create -output "${OUTPUT}/${FRAMEWORK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
# Copy the Swift module mappings for the simulator into the framework. The device mappings already exist from step 6.
cp -r "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${OUTPUT}/Modules/${FRAMEWORK_NAME}.swiftmodule"
# Delete build.
rm -rf "${BUILD_DIR}"
After generated, and added to the consumer app, the only thing left to do is to use Cocoapods in the consumer app to get Alamofire and SwiftyJSON.
I try to build static framework.
So I run following commands:
for device
xcodebuild
-project MyAppLib.xcodeproj
-sdk iphoneos
-target $PRODUCT_FRAMEWORK
-configuration Release clean build
for simulator
xcodebuild
-project MyAppLib.xcodeproj
-sdk iphonesimulator
-target $PRODUCT_FRAMEWORK
-configuration Release clean build
However when I try to run lipo:
lipo -create build/Release-iphonesimulator/MyAppLib.framework/MyAppLib
build/Release-iphoneos/MyAppLib.framework/MyAppLib
-output MyAppLib.framework
I get an error:
fatal error:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo:
build/Release-iphonesimulator/MyAppLib.framework/MyAppLib and
build/Release-iphoneos/MyAppLib.framework/MyAppLib have the same
architectures (i386) and can't be in the same fat output file
From Xcode I success to build it but from CLI both have the same structure.
$ file MyAppLib.framework gives me:
MyAppLib.framework: Mach-O universal binary with 4 architectures: [i386: current ar archive] [arm_v7: current ar archive] [x86_64: current ar archive] [arm64]
MyAppLib.framework (for architecture i386): current ar archive
MyAppLib.framework (for architecture armv7): current ar archive
MyAppLib.framework (for architecture x86_64): current ar archive
MyAppLib.framework (for architecture arm64): current ar archive
If I try to run only for one platform, the build is succeeded but I get strange framework that I cannot even explore it:
Edit:
Multiplatform script:
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
function build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
-target "${TARGET_NAME}" \
-configuration "${CONFIGURATION}" \
-sdk "${1}" \
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR="${BUILD_DIR}" \
OBJROOT="${OBJROOT}" \
BUILD_ROOT="${BUILD_ROOT}" \
SYMROOT="${SYMROOT}" $ACTION
}
function make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
xcrun lipo -create "${1}" "${2}" -output "${3}"
}
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo "Could not find other platform build directory."
exit 1
fi
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
Solution:
You do not have a problem. The MyAppLib.framework that appears after building your project is your static library, containing slices for all required architectures.
No need to use lipo after building your project.
Issue:
Your build script "automatically" builds your target twice, once for devices and once for simulators. Afterwards it merges both build artefacts to one fat lib (make_fat_library) using lipo.
Afterwards it copies the result around to:
#Ensure that the framework is present in both platform's build directories
As a result, you have a fat lib framework in both build directories.
Now you are trying to merge those two (already merged) fat libs. As both contain the same slices you get the error.
I want to create a private cocoapod of the output framework built for both simulators and devices.
I have created a Cocoa touch framework in which I have integrated CocoaLumberjack using Cocoapods.
I want to build the framework for all architectures possible (simulator as well as device).
By default the build setting,
'Build Active Architectures Only' is set to (Debug - Yes, Release - No).
As soon as I set this setting for debug to No, the build fails with the following linker error:
Undefined symbols for architecture i386:
"_OBJC_CLASS_$_DDASLLogger", referenced from:
objc-class-ref in MyManager.o
"_OBJC_CLASS_$_DDLog", referenced from:
objc-class-ref in MyManager.o
"_OBJC_CLASS_$_DDTTYLogger", referenced from:
objc-class-ref in MyManager.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I get it that CocoaLumberjack is available for only active architectures in debug version.
So I switch back the Build active architectures for debug to Yes and build the framework successfully.
To build the framework for all the architectures I am using a run script added in Build phases that also claims to merge the ios-device and ios-simulator build into one. Here is the script:
set -e
set +u
# Avoid recursively calling this script.
if [[ $SF_MASTER_SCRIPT_RUNNING ]]
then
exit 0
fi
set -u
export SF_MASTER_SCRIPT_RUNNING=1
# Constants
SF_TARGET_NAME=${PROJECT_NAME}
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# Take build target
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
then
SF_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
then
echo "Please choose iPhone simulator as the build target."
exit 1
fi
IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos
# Build the other (non-simulator) platform
xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION
xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION
# Copy the framework structure to the universal folder (clean it first)
rm -rf "${UNIVERSAL_OUTPUTFOLDER}"
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework"
# Smash them together to combine all architectures
lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}"
I check the 'Run script only when installing' checkbox present below the run script. Now I build the framework for Generic iOS device.
Now i click on the Products group present in the Project hierarchy and select the MyFramework.framework file, right click and select show in finder. It opens up the ~/Library/Developer/Xcode/DerivedData/MyFramework-dlpsipmxkqmemwgqrfeovlzgyhca/Build/Products/Debug-iphoneos/MyFramework.framework in finder.
Now, I create a new tag 'MyFramework-v0.0.1' containing the commit where I added MyFramework.framework file.
I go to ~/.cocoapods/repos/MyRepoName/ and create a podspec file MyFramework.podspec as follows:
Pod::Spec.new do |s|
s.name = "MyFramework"
s.version = "0.0.1"
s.summary = "A pod for MyFramework"
s.description = "A pod designed for MyFramework"
s.homepage = "My_Private_Repo_Path"
s.license = { :type => "MIT", :file => "FILE_LICENSE" }
s.authors = { "My_Username" => "my_email_address"
}
s.platform = :ios, "8.0"
s.ios.deployment_target = "8.0"
s.source = { :git => "My_Private_Repo_Path", :tag => 'MyFramework-v'+String(s.version) }
s.requires_arc = true
s.vendored_frameworks = "MyFramework.framework"
s.dependency "CocoaLumberjack"
end
Now when I run the following command in the terminal:
pod repo push MyRepoName MyFramework.podspec
I get the following Error:
Validating spec
-> MyFramework (0.0.1)
- ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use ` --verbose` for more information.
- NOTE | [iOS] xcodebuild: ld: warning: ignoring file MyFramework/MyFramework.framework/MyFramework, missing required architecture i386 in file MyFramework/MyFramework.framework/MyFramework (2 slices)
- NOTE | [iOS] xcodebuild: ld: warning: ignoring file MyFramework/MyFramework.framework/MyFramework, missing required architecture x86_64 in file MyFramework/MyFramework.framework/MyFramework (2 slices)
- NOTE | [iOS] xcodebuild: fatal error: lipo: -remove's specified would result in an empty fat file
[!] The `MyFramework.podspec` specification does not validate.
How to build cocoa touch framework for all devices and simulators, that is dependent on another framework (CocoaLumberjack) added using cocoapods? I need to create a private pod of the output framework.
So basically I found out that I just need to follow these simple steps.
1. Create a cocoa touch framework.
2. Set bitcode enabled to No.
3. Select your target and choose edit schemes. Select Run and choose Release from Info tab.
4. No other setting required.
5. Now build the framework for any simulator as simulator runs on x86 architecture.
6. Click on Products group in Project Navigator and find the .framework file.
7. Right click on it and click on Show in finder. Copy and paste it in any folder, I personally prefer the name 'simulator'.
8. Now build the framework for Generic iOS Device and follow the steps 6 through
9. Just rename the folder to 'device' instead of 'simulator'.
10. Copy the device .framework file and paste in any other directory. I prefer the immediate super directory of both.
So the directory structure now becomes:
- Desktop
- device
- MyFramework.framework
- simulator
- MyFramework.framework
- MyFramework.framework
Now open terminal and cd to the Desktop. Now start typing the following command:
lipo -create 'device/MyFramework.framework/MyFramework' 'simulator/MyFramework.framework/MyFramework' -output 'MyFramework.framework/MyFramework'
and that's it. Here we merge the simulator and device version of MyFramework binary present inside MyFramework.framework. We get a universal framework that builds for all the architectures including simulator and device.
Now, creating a pod for this framework doesn't make any difference. It works like a charm. Please also note that there are run scripts available too to achieve the same functionality, but I spent a lot of time in finding the correct script. So I would suggest you use this method.
XCode 11
First note that you can not use a fat framework with simulator support (x84_64 arch) for publishing to AppStore so you need to make two fat frameworks: one for Release with ARM archs (devices only) and one for Debug - ARM and x86_64 archs.
You can put next scripts to your projects's folder to make fat frameworks from a command line:
Makefile
BUILD_DIR = build
BUILD = #sh build.sh ${BUILD_DIR}
default:
#echo "Build framework makefile"
#echo "usage: make (release | debug | all | rebuild | clean)"
release:
${BUILD} Release YourTargetScheme # <- your target scheme
debug:
${BUILD} Debug YourTargetScheme
clean:
rm -r ${BUILD_DIR}
all: release debug
rebuild: clean all
build.sh
# Debug
set -x
# Params
BUILD_DIR=$1
CONFIGURATION=$2
SCHEME=$3
WORKSPACE=YourWorkspace.xcworkspace # <- your workspace file
DERIVED_DATA_PATH=$BUILD_DIR/DerivedData
# Destinations
IPNONEOS="generic/platform=iOS"
IPNONESIMULATOR="platform=iOS Simulator,name=iPhone 8"
# Build
if [ $CONFIGURATION = "Release" ]; then
xcodebuild \
-quiet \
-showBuildTimingSummary \
-workspace $WORKSPACE \
-configuration $CONFIGURATION \
-scheme $SCHEME \
-derivedDataPath $DERIVED_DATA_PATH \
-destination "$IPNONEOS"
else
xcodebuild \
-quiet \
-showBuildTimingSummary \
-workspace $WORKSPACE \
-configuration $CONFIGURATION \
-scheme $SCHEME \
-derivedDataPath $DERIVED_DATA_PATH \
-destination "$IPNONEOS" \
-destination "$IPNONESIMULATOR"
fi
# Move
FRAMEWORK=$SCHEME.framework
FRAMEWORK_PATH=$BUILD_DIR/$CONFIGURATION/$FRAMEWORK
mkdir $BUILD_DIR/$CONFIGURATION
rm -r $FRAMEWORK_PATH
if [ $CONFIGURATION = "Release" ]; then
mv $DERIVED_DATA_PATH/Build/Products/Release-iphoneos/$FRAMEWORK $FRAMEWORK_PATH
else
mv $DERIVED_DATA_PATH/Build/Products/Debug-iphoneos/$FRAMEWORK $FRAMEWORK_PATH
BINARY_FILE=$FRAMEWORK_PATH/$SCHEME
ARMV7=$FRAMEWORK_PATH/armv7
ARM64=$FRAMEWORK_PATH/arm64
x86_64=$FRAMEWORK_PATH/x86_64
lipo $BINARY_FILE -extract armv7 -o $ARMV7
lipo $BINARY_FILE -extract arm64 -o $ARM64
cp $DERIVED_DATA_PATH/Build/Products/Debug-iphonesimulator/$FRAMEWORK/$SCHEME $x86_64
lipo -create $ARMV7 $ARM64 $x86_64 -o $BINARY_FILE
# Clean
rm -rf $ARMV7 $ARM64 $x86_64
fi
Run one of these commands inside your project folder to build needed frameworks:
make all # Debug and Release frameworks
make release # Release only for devices and AppStore (armv7 and arm64 archs)
make debug # Debug with simulator support (armv7, arm64 and x86_64 archs)
Then you can find your fat frameworks in build directory inside your project's folder.