Both ios-arm64-simulator and ios-x86_64-simulator represent two equivalent library definitions

Seeing these when trying to create an xcframework for Apple Silicon that supports Mac Catalyst and the iOS Simulator:
  • Both ios-arm64-simulator and ios-x86_64-simulator represent two equivalent library definitions.

  • Both ios-arm64-maccatalyst and ios-x86_64-maccatalyst represent two equivalent library definitions.

Here's the command:
Code Block sh
xcodebuild -create-xcframework \
-framework ./xcframework-build/catalyst-x86_64/opencv2.framework \
-framework ./xcframework-build/catalyst-arm64/opencv2.framework \
-framework ./xcframework-build/osx-x86_64/opencv2.framework \
-framework ./xcframework-build/osx-arm64/opencv2.framework \
-framework ./xcframework-build/iphonesimulator-arm64/opencv2.framework \
-framework ./xcframework-build/iphonesimulator-x86_64/opencv2.framework \
-framework ./xcframework-build/iphoneos-arm64/opencv2.framework \
-output ./xcframework-build/opencv2.xcframework


From my understanding fat binaries for these frameworks isn't valid, but maybe it is in this case? These are static frameworks if that matters at all.

Using Xcode 12.2 RC.
Answered by cbrightpoint in 645963022
This is also the case for macOS architectures as well.

Both macos-x86_64 and macos-arm64 represent two equivalent library definitions.

It seems like using lipo for these combinations might be necessary:
  • ios-arm64-simulator and ios-x86_64-simulator

  • ios-arm64-maccatalyst and ios-x86_64-maccatalyst

  • macos-x86_64 and macos-arm64

What I'm not sure about is how these will end up in the xcframework subfolders.

edit: using lipo is necessary, and is the accepted solution for this.
Accepted Answer
This is also the case for macOS architectures as well.

Both macos-x86_64 and macos-arm64 represent two equivalent library definitions.

It seems like using lipo for these combinations might be necessary:
  • ios-arm64-simulator and ios-x86_64-simulator

  • ios-arm64-maccatalyst and ios-x86_64-maccatalyst

  • macos-x86_64 and macos-arm64

What I'm not sure about is how these will end up in the xcframework subfolders.

edit: using lipo is necessary, and is the accepted solution for this.

edit: using lipo is necessary, and is the accepted solution for this.

Can you provide some links to where this is documented and how we are supposed to accomplish this?

I am in the middle of getting some of our C++ dependencies to build with iOS arm64 simulator support and am failing when attempting to make an xcframework with all three slices.

Can you provide some links to where this is documented and how we are supposed to accomplish this?

Google?

I am in the middle of getting some of our C++ dependencies to build with iOS arm64 simulator support and am failing when attempting to make an xcframework with all three slices. 

There is one thing that hasn't been mentioned yet and that might be a big deal, especially with open-source C++ dependencies. Many of these tools are designed to be built on the platform where they run. Not all projects support cross-compilation. But even those projects that do support cross-compilation might not do it in a portable way. You have to make sure to check any generated header files. There may be platform and architecture-specific differences. xcframeworks are great for cross-platform versions, but you are expected to use lipo for for cross-architecture builds. But the headers for the different architectures may not be compatible. You will have to manually review all header differences and make adjustments to one or both projects until the headers are compatible.


Hi cbrightpoint,

could you please elaborate on your solution?
How do you create an xcframework for arm64 and x86_64?
Or are you only creating a fat binary with lipo?

Thanks,
Klaus
Code Block sh
# archive iPhone 64
xcodebuild archive -workspace ./UnitTestWithPod.xcworkspace -scheme UnitTestWithPod -arch arm64 -configuration Release SKIP_INSTALL=NO -sdk "iphoneos" BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHERCFLAGS="-fembed-bitcode" -archivePath ./archives/ios.xcarchive
# archive simulator arm64 M1
xcodebuild archive -workspace ./UnitTestWithPod.xcworkspace -scheme UnitTestWithPod -arch arm64 -configuration Release SKIP_INSTALL=NO -sdk iphonesimulator BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHERCFLAGS="-fembed-bitcode" -archivePath ./archives/sim64.xcarchive
# archive simulator x86_64
xcodebuild archive -workspace ./UnitTestWithPod.xcworkspace -scheme UnitTestWithPod -arch x86_64 -configuration Release SKIP_INSTALL=NO -sdk iphonesimulator BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHERCFLAGS="-fembed-bitcode" -archivePath ./archives/simx86.xcarchive
xcodebuild -create-xcframework \
-framework "./archives/ios.xcarchive/Products/Library/Frameworks/UnitTestWithPod.framework" \
-framework "./archives/sim64.xcarchive/Products/Library/Frameworks/UnitTestWithPod.framework" \
-framework "./archives/simx86.xcarchive/Products/Library/Frameworks/UnitTestWithPod.framework" \
-output "./archives/UnitTestWithPod.xcframework"


As a result
Code Block sh
Both ios-x86_64-simulator and ios-arm64-simulator represent two equivalent library definitions.


I have also tryied lipo to create fat framework for this sim64 and simx86 and then create xcframework. xcframework has beed generated.
I was able to run on
  • arm64 simulator (m1)

  • arm64 device

But I was NOT able to run on
  • x86 simulator with error

Code Block sh
'xyz' lib is unavailable: cannot find swift declaration for this class


I was able to get xcframework + simulator fat binary working. I needed to have an xcframework that supported simulator on both arm64 and x86_64 because I'm developing on an M1 and my CI provider wants x86_64 to run unit tests. (Oh, and having an xcframework that can be used to develop on Intel might come in handy at some point.)

My steps:
  1. Build three frameworks (in my case, arm64, arm64-simulator, x86_64-simulator)

  2. Build xcframework with arm64 and arm64-simulator frameworks

  3. Use lipo to add x86_64-simulator binary to the arm64-simulator binary (which is inside my xcframework)

That's it. I ended up modifying my xcframework's Info.plist file to document the fact that the second "library" was a fat binary (adding "x86_64" to the "SupportedArchitectures" array) but that doesn't appear to be necessary.
@bgeerdes Post solved my problem.
@bgeerdes Can you please give more details about what you did? When I try this I get "A library with the identifier "ios-arm64" already exists"

What I tried:
  • Compile sources with clang++

  • Link static libraries with libtool (one for each arch)

  • Create frameworks from libs with lipo

  • Create XCFramework from frameworks

I also tried to combine arch in different ways, but I never got it to run in the M1 simulator (except with Rosetta enabled)

Now I got to the "Both ios-arm64-simulator and ios-x86_64-simulator represent two equivalent library definitions." part. 🤬

If I remove one, I get "Both ios-arm64 and ios-armv7 represent two equivalent library definitions."

How is this supposed to work for clang/lipo created frameworks? I can't find any documentation or anyone who wrote down how it should work 😢


Code Block
$ xcodebuild -create-xcframework \
-framework build/libzip/iphoneos/arm64/libzip.framework \
-framework build/libzip/iphoneos/armv7/libzip.framework \
-framework build/libzip/iphonesimulator/arm64/libzip.framework \
-framework build/libzip/iphonesimulator/x86_64/libzip.framework \
-output app/Frameworks/libzip.xcframework
Both ios-arm64-simulator and ios-x86_64-simulator represent two equivalent library definitions.
make: *** [app/Frameworks/libzip.xcframework] Error 70


Code Block
$ lipo -detailed_info build/libzip/iphoneos/arm64/libzip.framework/libzip
Fat header in: build/libzip/iphoneos/arm64/libzip.framework/libzip
fat_magic 0xcafebabe
nfat_arch 1
architecture arm64
cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64_ALL
capabilities 0x0
offset 32
size 2215800
align 2^3 (8)
$ lipo -detailed_info build/libzip/iphoneos/armv7/libzip.framework/libzip
Fat header in: build/libzip/iphoneos/armv7/libzip.framework/libzip
fat_magic 0xcafebabe
nfat_arch 1
architecture armv7
cputype CPU_TYPE_ARM
cpusubtype CPU_SUBTYPE_ARM_V7
capabilities 0x0
offset 28
size 2140936
align 2^2 (4)
$ lipo -detailed_info build/libzip/iphonesimulator/arm64/libzip.framework/libzip
Fat header in: build/libzip/iphonesimulator/arm64/libzip.framework/libzip
fat_magic 0xcafebabe
nfat_arch 1
architecture arm64
cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64_ALL
capabilities 0x0
offset 32
size 982720
align 2^3 (8)
$ lipo -detailed_info build/libzip/iphonesimulator/x86_64/libzip.framework/libzip
Fat header in: build/libzip/iphonesimulator/x86_64/libzip.framework/libzip
fat_magic 0xcafebabe
nfat_arch 1
architecture x86_64
cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
capabilities 0x0
offset 32
size 977840
align 2^3 (8)




You can think of an xcframework as a sumo "binary".

Here's how I understand things after going though this a few times:

  1. An xcframework is for bundling multiple platforms together into a distributable binary package. You can put ios, ios-simulator, ios-macabi, darwin (macos), and tvos (and watchOS and carPlayOS and their respective simulator) platform code together into an xcframework. Those are your "library definitions". So the maximum number of frameworks (.framework) or libraries (.a) that can go in an xcframework at the top level is limited to the total number of platforms. And they all must be targeting different system interfaces (platforms) which means you can't put two binaries expecting e.g. the ios-simulator system interface into an xcframework at the top level.
  2. In order to support the different architectures on each platform, you must lipo those architecture slices together to make a fat binary. Generally the options for those, on modern Apple hardware, are x86_64, and arm64v8 (aarch64). You may also need armv7 and i386 if you're targeting older hardware. The same rule goes for lipo, you can only have one instance of any given architecture slice in a fat binary which means you can't put two binaries targeting e.g. x86_64 hardware into a fat binary at the top level.

There is some literature suggesting that lipo is not an Apple tool and is not supported (3rd and 4th bullet point under Motivation & consequences section: https://awesomeopensource.com/project/bielikb/xcframeworks). I believe that to be inaccurate. lipo is distributed with Xcode, by Apple:

❯ lipo
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: ...
...

That resource is otherwise somewhat helpful and much appreciated. I believe the misunderstanding is that Xcode now does the heavy lifting so that you are no longer manually required to add special build phases and invoke lipo yourself anymore (unless you're not using Xcode, of course).

Look at the folders in the xcframework (other folders omitted for brevity):

❯ tree
.
└── Foo.xcframework
    ├── Info.plist
    ├── ios-arm64
    │   └── libfoo-ios.a
    ├── ios-arm64_x86_64-maccatalyst
    │   └── libfoo-ios-macabi.a
    ├── ios-arm64_x86_64-simulator
    │   └── libfoo-ios-sim.a
    └── macos-arm64_x86_64
        └── libfoo-macos.a

Clearly Xcode is combining architecture slices when building frameworks that target e.g. ios-simulator. So this strategy seems to be both supported and the intended way to distribute a framework that can be used with multiple architectures.

2dcowuno:

There is some literature suggesting that lipo is not an Apple tool and is not supported (3rd and 4th bullet point under Motivation & consequences section: https://awesomeopensource.com/project/bielikb/xcframeworks). I believe that to be inaccurate.

This info was corrected. Thanks for pointing that inaccurate statement out.

You don't have to manually create the fat simulator framework with lipo, Xcode will do if for you if you ask right;

SCHEME_NAME="MyScheme"
FRAMEWORK_NAME="MyFramework"

SIMULATOR_ARCHIVE_PATH="${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphonesimulator.xcarchive"
DEVICE_ARCHIVE_PATH="${BUILD_DIR}/${CONFIGURATION}/${FRAMEWORK_NAME}-iphoneos.xcarchive"

OUTPUT_DIR="./Products/"

# Simulator xcarchive (arm64 + x86_64)
xcodebuild archive \
  ONLY_ACTIVE_ARCH=NO \
  -scheme ${SCHEME_NAME} \
  -project "${SCHEME_NAME}.xcodeproj" \
  -archivePath ${SIMULATOR_ARCHIVE_PATH} \
  -sdk iphonesimulator \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
  SKIP_INSTALL=NO

# Device xcarchive (arm64)
xcodebuild archive \
  -scheme ${SCHEME_NAME} \
  -project "${SCHEME_NAME}.xcodeproj" \
  -archivePath ${DEVICE_ARCHIVE_PATH} \
  -sdk iphoneos \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
  SKIP_INSTALL=NO

# Clean-up any existing instance of this xcframework from the Products directory
rm -rf "${OUTPUT_DIR}${SCHEME_NAME}.xcframework"

# Create final xcframework
xcodebuild -create-xcframework \
  -framework ${DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \
  -framework ${SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework \
  -output ${OUTPUT_DIR}/${FRAMEWORK_NAME}.xcframework

Since some people asked for exact steps on how to create the fat framework with lipo, here is how I did it:

# Create a template framework starting from the arm64 ios simulator one
mkdir -p build-iossim
cp -r build-iossim-arm64-release/target/MyFramework.framework build-iossim

# Merge the arm64 and x86_64 binaries with lipo
xcrun lipo -create -output build-iossim/MyFramework.framework/MyFramework build-iossim-x86_64-release/target/MyFramework.framework/MyFramework build-iossim-arm64-release/target/MyFramework.framework/MyFramework

# Codesign the fat binary
xcrun codesign --sign - build-iossim/MyFramework.framework/MyFramework

# Create the xcframework merging the ios_arm64 framework and the fat simulator framework
mkdir -p build-xcframework
xcodebuild -create-xcframework \
    -framework build-ios-arm64-release/target/MyFramework.framework \
    -framework build-iossim/MyFramework.framework \
    -output "build-xcframework/MyFramework.xcframework"
Both ios-arm64-simulator and ios-x86_64-simulator represent two equivalent library definitions
 
 
Q