Skip to content

Instantly share code, notes, and snippets.

@eladnava
Last active November 26, 2021 14:43
Show Gist options
  • Save eladnava/0824d08da8f99419ef2c7b7fb6d4cc78 to your computer and use it in GitHub Desktop.
Save eladnava/0824d08da8f99419ef2c7b7fb6d4cc78 to your computer and use it in GitHub Desktop.
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 6' 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
@mknippen
Copy link

mknippen commented Apr 25, 2018

Upon further review, neither of these options appears to create a build that allows me to submit to the App Store. Using the option above works great for simulator and device, but when submitting to the App Store, I get several errors:

ITMS-90087: Unsupported Architectures. The framework contains unsupported architectures [x86_64, i386]'.
ITMS-90635: Invalid Mach-O format. The format in X.framework is not consistent with the Mach-O in the main bundle. The main bundle Mach-O contains armv7(bitcode) and arm64(bitcode), while the nested bundle Mach-O contains X86_64ALL(machine code). Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting.
ITMS-90209: Invalid Segment Alignment. The app binary at DIR/X.framework/X does not have proper segment alignment. Try rebuilding the app with the latest Xcode version.

@lottadot
Copy link

@mknippen You'd have to strip the x86_64 and/or i386 from the frameworks you're using in order to submit to the App Store. Often you'll see people add a build phase script item to check if the current build configuration is RELEASE and then if so iterate through frameworks that are linked in and remove the x86 slices from them. You may find it easier if you do this with an app-store-specific build scheme.

@ohtwo
Copy link

ohtwo commented Jul 11, 2018

Add below code If want to zip sequence into the script.

# Step 6. Create zip
cd ${PROJECT_DIR}

mkdir Build
cp LICENSE Build
cp -rf ${FULL_PRODUCT_NAME} Build

rm -rf "${FULL_PRODUCT_NAME}.zip"
zip -r "${FULL_PRODUCT_NAME}.zip" Build
rm -rf ${FULL_PRODUCT_NAME} Build

@Jane930525
Copy link

Didn't find the framework generated under universal framework but was found in both simulator and iPhone individually

@nick3389
Copy link

nick3389 commented Aug 7, 2018

@eladnava the script seems to not be working in XCode 10... It seems that the .framework does not exist under Release-universal folder. Is there any suggestion?

@iwasrobbed-ks
Copy link

iwasrobbed-ks commented Aug 7, 2018

A few things:

  1. Thanks for posting this! It was really helpful
  2. This script can also be done completely using the command line's archive option on xcodebuild; doing it as a post-archive in Xcode's schemes is pretty opaque because you don't get a sense of build progress like you do in the command line
  3. You aren't lipo'ing the dSYM files into a universal dSYM but it's easy to add. Personally, I'd want to ensure all frameworks are distributed with dSYM files so crashes can be symbolicated

Here's an example script from where we're creating a universal iOS .framework and .framework.dSYM and bundling that with a macOS build into one zip file that can be used in a .podspec

Custom script (with dSYM lipo step as well): https://github.com/KeepSafe/ObjectiveRocks/blob/frameworks/build_universal_framework.sh
Podspec example: https://github.com/KeepSafe/ObjectiveRocksFramework/blob/master/ObjectiveRocksFramework.podspec

Another custom example: https://github.com/AudioKit/AudioKit/blob/master/Frameworks/build_frameworks.sh

@nick3389
Copy link

nick3389 commented Aug 8, 2018

@rob-keepsafe we ran this script from the project's folder? Also, does this produce a universal framework for both simulators and devices? Thank you!

@fer662
Copy link

fer662 commented Sep 17, 2018

@nick3389 seeing the same result with xcode 10.

@BalaKarunakaran
Copy link

The Script is not working in Xcode 10. Its opens OUTPUTFOLDER, But the Universal Framework is not generated in that folder. Anything need to change in the Script. @eladnava.

@nick3389
Copy link

nick3389 commented Sep 18, 2018

@eladnava @fer662 @BalaKarunakaran

It seems that in XCode 10, the folder under Release-universal path does not contain the {YOUR_FRAMEWORK}.framework file but its contents. I solved it by replacing this line under echo "Copying to output folder":

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/"

with this one:

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}" "${UNIVERSAL_OUTPUTFOLDER}/"

Please, confirm that it works for you too.

@BalaKarunakaran
Copy link

@nick3389 Great, Its working fine. 👏

@donygeorgek
Copy link

This used to work in Xcode 8.3.3
It is not working in Xcode 9.4.1. The generated framework does not have the i386 & x86_64 files. Any update needed in script?

@macbellingrath
Copy link

@nick3389 👍 thanks!

@Jignesh1805
Copy link

Hi all of you ,
Please can you help me .
Regarding third party framework add in podspec file .

@IvanovDeveloper
Copy link

@eladnava @fer662 @BalaKarunakaran

It seems that in XCode 10, the folder under Release-universal path does not contain the {YOUR_FRAMEWORK}.framework file but its contents. I solved it by replacing this line under echo "Copying to output folder":

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/"

with this one:

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}" "${UNIVERSAL_OUTPUTFOLDER}/"

Please, confirm that it works for you too.

It's work for xcode 10

@ignaval
Copy link

ignaval commented Oct 22, 2018

@eladnava @fer662 @BalaKarunakaran

It seems that in XCode 10, the folder under Release-universal path does not contain the {YOUR_FRAMEWORK}.framework file but its contents. I solved it by replacing this line under echo "Copying to output folder":

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/"

with this one:

cp -R "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}" "${UNIVERSAL_OUTPUTFOLDER}/"

Please, confirm that it works for you too.

I actually had to change a different line for this to work on Xcode 10 (Version 10.0 (10A255)). I added a missing target name. But kept the original line you mentioned changing. I just changed:

lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${EXECUTABLE_PATH}"

for:

lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}/${EXECUTABLE_PATH}" "${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${EXECUTABLE_PATH}"

and it worked correctly.

@canisterexister
Copy link

canisterexister commented Oct 26, 2018

If still not working check AlexM answer https://stackoverflow.com/a/52873099/4311935

@antonc27
Copy link

Just want to notice that script will not work as is if you have different target and product names (e.g., target MyLib_iOS and product MyLib.framework). This can be fixed by using $PRODUCT_NAME instead of $TARGET_NAME (except of -scheme parameter)

@GriffinStudios
Copy link

GriffinStudios commented Apr 29, 2019

@eladnava thanks so much for creating this! This and your article at https://eladnava.com/publish-a-universal-binary-ios-framework-in-swift-using-cocoapods/ was amazing.

--
Here is the change I made to get it to working:

Make ${ARCHIVE_PRODUCTS_PATH}${INSTALL_PATH}/${FULL_PRODUCT_NAME} to ${ARCHIVE_PRODUCTS_PATH}/${INSTALL_PATH}/

Why?

  • products_path and install_path needed a file slash otherwise it doesn’t reference the correct file.
  • Including the full_product_name causes ONLY the framework contents to be copied.

——
Also, as you’ll probably have to modify this Xcode script to fit your particular framework, here's three debugging tips:

  1. The file at /tmp/${PROJECT_NAME}_archive.log is a lifesaver for trouble-shooting efforts. Every archive, you should be referencing this file.

  2. I highly suggest adding this echo statement to the file as this path is were 99% of your problems will occur: echo "Framework location: ${ARCHIVE_PRODUCTS_PATH}/${INSTALL_PATH}/“

  3. If the error log tries a cp command and "file can't be found" then it is most likely a path error.

@aevapps
Copy link

aevapps commented Sep 24, 2019

For Xcode 11 change 'platform=iOS Simulator,name=iPhone 6' to 'platform=iOS Simulator,name=iPhone 8'

@caiovncius
Copy link

I had some issues in copy files. I resolved with this code:

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"


echo "Building for iPhoneSimulator"
xcodebuild -workspace "${WORKSPACE_PATH}" -scheme "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6' 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

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

# 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"
echo ${FULL_PRODUCT_NAME}
cp -Rf "${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME}" "${PROJECT_DIR}"

open "${PROJECT_DIR}"

fi

@nick3389
Copy link

nick3389 commented May 15, 2020

@caiovncius environment variables are not working since XCode 11.4. I just try to echo "${TARGET_NAME}" but the result is blank. Any thought about this? It only works if I embed it as a post archive script.

@zhoulijun12315
Copy link

in xCode12 , the result is blank

@GeorgeVashakidze
Copy link

in xCode12 , the result is blank - SAME HERE : )) what the hell : ))

@avjiang
Copy link

avjiang commented Jan 12, 2021

Hello i am currently learning about framework, and faced the issue with blank result.

I am using Xcode 12

@shawnkc
Copy link

shawnkc commented Jun 29, 2021

Comment out the first line if your results are blank. It redirects the output to a file.

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