I am trying to recompile the bitcode of my tvOS app, including the bundled WebRTC framework, for the purpose of being able to successfully submit the app to the App Store. This same process with the iOS app succeeds.
Upon pushing the tvOS app to the App Store, an email alerts me that:
ITMS-90562: Invalid Bundle - One or more dynamic libraries that are referenced by your app are not present in the dylib search path.
To diagnose the issue, I switched to an Ad Hoc distribution with bitcode rebuilding locally. I did this with both iOS and tvOS so that I could compare the resulting log files.
The earliest difference in the process is this, seen below, where it is apparently putting together a linkage graph.
iOS
Scanning IPA...
Complete LinkageGraph:
Streamie.app/PlugIns/StreamieNotificationService-iOS.appex/StreamieNotificationService-iOS arm64 ->
Streamie.app/Streamie arm64 ->
Streamie.app/Frameworks/WebRTC.framework/WebRTC arm64
Streamie.app/Frameworks/WebRTC.framework/WebRTC arm64 ->
tvOS (note the warning...)
Scanning IPA...
warning: Failed to resolve linkage dependency Streamie arm64 -> #rpath/WebRTC.framework/WebRTC: Could not find MachO for /private/var/folders/43/rmt6yww10fg1dbs9tzlfwvlh0000gn/T/XcodeDistPipeline.~~~nvA8Ku/Root/Payload/Streamie.app/Frameworks/WebRTC.framework/WebRTC
Complete LinkageGraph:
Streamie.app/Streamie arm64 ->
$ otool -L WebRTC.framework/WebRTC (system libs and such removed)
WebRTC.framework/WebRTC (architecture arm64):
#rpath/WebRTC.framework/WebRTC (compatibility version 0.0.0, current version 0.0.0)
$ file WebRTC.framework/WebRTC
/[snip]/webrtc/WebRTC.framework/WebRTC: Mach-O universal binary with 1 architecture: [arm64:Mach-O 64-bit dynamically linked shared library arm64]
/[snip]/WebRTC.framework/WebRTC (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64
When the bitcode building process finally fails, the log file concludes with:
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:372:in `run'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:2886:in `block in CompileOrStripBitcodeInBundle'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:2825:in `each'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:2825:in `CompileOrStripBitcodeInBundle'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:3112:in `block in ProcessIPA'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:3073:in `each'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:3073:in `ProcessIPA'
/Applications/Xcode.app/Contents/Developer/usr/bin/ipatool:4035:in `<main>'" UserInfo={NSLocalizedDescription=ipatool failed with an exception: #<CmdSpec::NonZeroExitException: $ /Applications/Xcode.app/Contents/Developer/usr/bin/python3 /Applications/Xcode.app/Contents/Developer/usr/bin/bitcode-build-tool -v -t /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin -L --sdk /Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.2.sdk -o /var/folders/43/rmt6yww10fg1dbs9tzlfwvlh0000gn/T/ipatool20220214-66770-pw6k3b/thinned-out/arm64/Payload/Streamie.app/Streamie --generate-dsym /var/folders/43/rmt6yww10fg1dbs9tzlfwvlh0000gn/T/ipatool20220214-66770-pw6k3b/thinned-out/arm64/Payload/Streamie.app/Streamie.dSYM --strip-swift-symbols /var/folders/43/rmt6yww10fg1dbs9tzlfwvlh0000gn/T/ipatool20220214-66770-pw6k3b/thinned-in/arm64/Payload/Streamie.app/Streamie
Status: pid 66874 exit 1
[snip thousands of lines]
Search Path: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/13.0.0/lib/darwin, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/appletvos, /Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.2.sdk/usr/lib, /Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.2.sdk/System/Library/Frameworks
WebRTC not found in dylib search path
Some additional notes:
I can build-and-run to an Apple TV without issue.
I can archive the app and side-load it, and run it on an Apple TV just fine.
The resulting iOS and tvOS WebRTC frameworks are each around 620MB, which is roughly the expected size with bitcode enabled.
To build WebRTC targeting tvOS, I'm using a modification of this script, seen below, combined with the build script here.
#!/bin/bash
GN_OUT_PATH=$1
${GN_OUT_PATH}/obj/pc/peerconnection.ninja | awk '{ print $47 }')
IPHONE_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk
TV_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.2.sdk
IPHONE_SYSROOT_SED=$(echo "${IPHONE_SYSROOT//\//\\/}")
TV_SYSROOT_SED=$(echo "${TV_SYSROOT//\//\\/}")
ninjas=`find ${GN_OUT_PATH} -name '*.ninja'`
ninjas=$(echo "$ninjas" | tr ' ' '\n' | sort -u | tr '\n' ' ')
for ninja in $ninjas; do
sed -i -- "s/${IPHONE_SYSROOT_SED}/${TV_SYSROOT_SED}/g" $ninja
sed -i -- "s/iphoneos-version/appletvos-version/g" $ninja
sed -i -- "s/arm64-apple-ios/arm64-apple-tvos/g" $ninja
done
My best guess at this point is that somehow the bitcode support in the WebRTC framework (for tvOS) is broken, but I don't know how verify that.
Related
I have a makefile that builds some C files and if I run it on an M1 mac the resulting library has the architecture arm64 which I thought it what is necessary for them to compile with an Xcode project for iOS. I discovered I can run the command otool -l libf2c.a | grep platform which should tell me what it was compiled for and in my case it returns platform1 which indicates macOS. Based on this, I think I need a value of platform2 for iOS.
The reason this is an issue is because in Xcode I get the error ld: building for iOS, but linking in object file built for macOS, file '/Users/e.../close.o' for architecture arm64.
Based on what I have been researching it seems iOS and macOS have the same architecture (arm64) but are a different 'platform'? But, I am not sure how the platform is determined. Is there some setting in my makefile I need to specify the platform? I am assuming that if I am able to get the platform to be iOS then Xcode will cooperate and be able to build the library I have generated.
The preferred way to compile for iOS via command line would probably be to use the xcrun command. This will allow you to specify the correct SDK for the platform you actually want to run on. For example:
prompt$ xcrun --sdk iphoneos --toolchain iphoneos clang -c test.c -o test.o -arch arm64
prompt$ otool -v -l test.o | grep platform
platform IOS
TL;DR: change your compiler invocation from plain clang to xcrun --sdk iphoneos --toolchain iphoneos clang.
This question already has answers here:
Building for iOS Simulator, but the linked framework '****.framework' was built for iOS
(11 answers)
Closed 2 years ago.
I have an app using a linked and embedded custom framework. The app built properly for iOS devices and simulators until Xcode 12.2. Starting from Xcode 12.3 however, I'm getting the following error:
Building for iOS Simulator, but the linked and embedded framework 'My.framework' was built for iOS + iOS Simulator.
The framework is built for both devices and simulators (as the error actually says) and merged using lipo, so it should be able to run everywhere without issues.
Am I missing something here? Is there a relevant change in Xcode 12.3?
I'm afraid that this is actually the correct error and the framework shouldn't contain iOS and iOS Simulator code at the same time. Apple tries to force us to use XCFrameworks for this purpose. They started it in Xcode 11 and just tightened up the restrictions.
The only correct way to resolve this is to rebuild the framework as an XCFramework. Which is easy to do:
xcrun xcodebuild -create-xcframework \
-framework /path/to/ios.framework \
-framework /path/to/sim.framework \
-output combined.xcframework
You can start with a combined .framework. Make two copies of the framework, and use lipo to remove the slices from the binary that are associated with a different SDK.
It is based on the original answer from Apple here.
My particular case is that I'm getting this error using Rome, which produces these frameworks (a possible solution is here). Also, a lot of struggling is going on on the Carthage side.
You have to exclude device architectures while building for simulator and while building for the device you have to exclude simulator's architectures.
To do that, navigate to Build Settings of your project -> Excluded Architectures -> Select the configuration(Debug/Release/Etc...) -> Tap + -> Any iOS Simulator SDK -> Add arm64, arm64e, armv7
Similarly, add x86_64, i386 to Any iOS SDK.
PS: You can check all the architectures which are present in your framework by running file <path_to_framework_binary> or lipo -info <path_to_framework_binary>.
Ex. file /Users/srikanth.kv/MyProject/MyLibrary.framework/MyLibrary
I have a framework with a universal binary that contains x86_64 and arm64 which I merge with lipo with a custom script at framework build time. I encountered this same issue for Xcode 12.3 and have created a work around for now. Hopefully this will get fixed in Xcode quickly, but until then, one quick fix would be to thin the architectures and use the framework that you need.
Please see my answer here on how to start producing .xcframeworks which is the long term solution for framework authors
For instance, let's assume I'm in a terminal in the working directory where my universal framework some_framework.framework is. If I want to run on an actual physical device, I execute the following command:
lipo -thin arm64 some_framework.framework/some_framework -output some_framework
With the above command, you extract the arm64 binary. Afterwards, replace the current some_framework.framework/some_framework with the newly generated arm64 only binary
mv some_framework some_framework.framework
If you have a universal framework built only from Objective-C sources, your job is done. But if you've got Swift code too, then you would need to update some_framework.framework/Modules/some_framework.swiftmodule so that it does not contain any references to architectures that are not arm64.
You would follow a similar process for running on the simulator except that you need x86_64. I'm currently now maintaining two versions of my framework until this is fixed. Whenever I switch between the simulator and the device, I simply switch out which framework is in my project.
In addition to mistahenry's answer, you can handle this automatically in your project with this workaround.
Set your Universal framework that does not work in Xcode 12.3 to Do not embed (in General → Frameworks, Libraries and Embedded Content)
Add this "new run script phase" in "Build Phases":
FRAMEWORK_APP_PATH="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
# 1. Copying FRAMEWORK to FRAMEWORK_APP_PATH
find "$SRCROOT" -name '*.framework' -type d | while read -r FRAMEWORK
do
if [[ $FRAMEWORK == *"MY_WONDERFUL_UNIVERSAL_FRAMEWORK.framework" ]]
then
echo "Copying $FRAMEWORK into $FRAMEWORK_APP_PATH"
cp -r $FRAMEWORK "$FRAMEWORK_APP_PATH"
fi
done
# 2. Loops through the frameworks embedded in the application and removes unused architectures.
find "$FRAMEWORK_APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
if [[ $FRAMEWORK == *"MY_WONDERFUL_UNIVERSAL_FRAMEWORK.framework" ]]
then
echo "Strip invalid archs on: $FRAMEWORK"
FRAMEWORK_EXECUTABLE_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleExecutable" "$FRAMEWORK/Info.plist")
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[#]}"
rm "${EXTRACTED_ARCHS[#]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements $FRAMEWORK_EXECUTABLE_PATH
else
echo "Ignored strip on: $FRAMEWORK"
fi
done
Replace MY_WONDERFUL_UNIVERSAL_FRAMEWORK by the name of your framework and be sure that it is located at SRCROOT
My static lib is built with xcodebuild and then a fat lib is created from the simulator and device build result.
Here is my xcodebuild command:
xcodebuild OTHER_CFLAGS="-fembed-bitcode" -configuration "iphoneos" -target "${LIB_NAME}Common" -sdk iphoneos
xcodebuild OTHER_CFLAGS="-fembed-bitcode" -configuration "iphonesimulator" -target "${LIB_NAME}Common" -sdk iphonesimulator
lipo command:
lipo -create "${DEVICE_DIR}/lib${LIB_NAME}Common.a" "${SIMULATOR_DIR}/lib${LIB_NAME}Common.a" -output "${INSTALL_DIR}/include/${LIB_NAME}/lib${LIB_NAME}Common.a"
After checking the architectures in the fat lib, I got:
$ lipo -info MyLibCommon.a
Architectures in the fat file: MyLibCommon.a are: armv7 i386 x86_64 arm64
However, when I add the lib to a project via cocoapods and run the project on Apple's new Silicon (with arm64 chipset) on simulator, I got the following compile error:
building for iOS Simulator, but linking in object file built for iOS, file 'MyLibCommon.a' for architecture arm64
Excluding the arm64 architecture for Simulator is not an option because on Apple Silicon Mac has arm64 chipset.
Any idea how can I build a static library for Apple Silicon Simulator?
This is not possible.
In all likelihood, your simulator binaries are just i386 and x86_64. If you truly had arm64 iOS binaries and arm64 macOS binaries, lipo would error out on you:
fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: test.a.ios and test.a.macos have the same architectures (arm64) and can't be in the same fat output file
This happens no matter whether you try it with full-fledged binaries, unlinked object files or static libraries. The reason is simply a shortcoming in the fat file format: you can only have one slice per architecture. You want both arm64 iOS and the Apple Silicon simulator, but that would be 2x arm64.
You might be tempted to try and build a single thin arm64 binary that works on both iOS and macOS, but that too is impossible. Binaries are platform-locked:
% otool -l test.o.ios | fgrep -B1 -A5 LC_BUILD_VERSION
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 2
sdk 14.2
minos 14.2
ntools 0
% otool -l test.o.macos | fgrep -B1 -A5 LC_BUILD_VERSION
Load command 1
cmd LC_BUILD_VERSION
cmdsize 24
platform 1
sdk 11.0
minos 11.0
ntools 0
Notice the platform 2 vs platform 1. The kernel will actually ignore this load command, but dyld will not. And you can't have two such load commands in a single binary either, that's considered invalid as well.
You might also remember talk of a "Universal 2" file format from Apple's announcement or something citing that - but they lied. There is no "universal 2", it's exactly the same file format it was a decade ago. When they say "universal 2", all they mean is "adding an arm64 slice to your macOS binary".
The way I see it, you have three options:
You build separate libraries and keep the names separate.
You never build both architectures at the same time.
You build the simulator target for x86_64 and run it under Rosetta.
The latter two are both being suggested widely across the internet, with number 2 as "build only for active architecture" and number 3 being "exclude arm64". Given that Rosetta is expected to go away eventually, the third option seems not viable for the long term.
My work is blocked by 'ld: warning: URGENT: building for iOS simulator, but linking in object file built for OSX' or vice versa, so I want to find out which platform a lib was built for.
I tried:
file tmp/openssl/lib/libcrypto.dylib
lipo -info tmp/openssl/lib/libcrypto.dylib
otool -hv -arch all tmp/openssl/lib/libcrypto.a
otool -hv -arch all /usr/local/opt/openssl/lib/libcrypto.a
and only thing I got is CPU type x86_64 or i386.
As originally described in this post, you can run otool -l /path/to/binary and inspect output for 'Load' commands. Output should contain LC_VERSION_MIN_IPHONEOS command for iOS binaries, and LC_VERSION_MIN_MACOSX for macOS.
In my search for a method to determine if a iOS binary was build with Bitcode, I found the following post:
How to check if a framework is BITCODE supported for Xcode7
Here, the following method was suggested to determine if bitcode is present in a binary:
$ otool -l libName.o | grep __LLVM
However, I have tried this on several binaries with no success. One of them is a library I know has bitcode since after I changed the flag on its project a build error went away. Another one of them is a binary for a file extension, build using Archive. And another is for apple watch.
I believe all of the above binaries should have Bitcode, and yet I always get no results from the above command.
Does anyone know any other method that works with the latest binaries?
I'm using XCode 7.2 and 10.10.5 in case it matters.
UPDATE: Here is an example of a file which is supposed to have bitcode but the above command doesn't return anything. It is a binary from a test File Provider. I generated it via Archive and Deploy as Ad Hoc, and made sure the setting for bitcode was on for the target.
https://www.dropbox.com/s/eyuzs5j1t7nsq6t/CustomDocumentProviderFileProvider?dl=0
If you have a fat binary, then you need to run otool -l on a specific slice. For instance, in the following example I chose arm64:
otool -arch arm64 -l MyFramework.framework/MyFramework | grep -a4 __LLVM
In the output you should check:
if there is at least one section named __LLVM
if the size is greater than zero
This seems to be a problem with otool as reported here. Use file to get a list of architectures and then supply the architecture to otool. Given a fat binary with Bitcode for armv7, arm64, i386 and x86_64:
$ file lib.a
lib.a: Mach-O universal binary with 4 architectures
lib.a (for architecture armv7): current ar archive random library
lib.a (for architecture i386): current ar archive random library
lib.a (for architecture x86_64): current ar archive random library
lib.a (for architecture arm64): current ar archive random library
$ otool -arch armv7 -l lib.a | grep bitcode
sectname __bitcode
According to this question, otool does not report Bitcode for x86_64 and i368.
CustomDocumentProviderFileProvider does not seem to contain Bitcode:
$ file CustomDocumentProviderFileProvider
CustomDocumentProviderFileProvider: Mach-O universal binary with 2 architectures
CustomDocumentProviderFileProvider (for architecture armv7): Mach-O executable arm
CustomDocumentProviderFileProvider (for architecture arm64): Mach-O 64-bit executable
$ otool -arch armv7 -l CustomDocumentProviderFileProvider | grep bit
$
Disclaimer: I'm the author of LibEBC.
You can use ebcutil to determine whether bitcode is present in any binary (Mach-O, ELF) or library (.a/.dylib/.so).
https://github.com/JDevlieghere/LibEBC
As of today, the technique which works for me is the one mentioned in this answer from another SO thread. Specifically, for a (dynamic) framework named MyLib and containing those two device architectures:
otool -arch armv7 MyLib.framework/MyLib | grep LLVM
otool -arch arm64 MyLib.framework/MyLib | grep LLVM