Xcode: CMake does not generate projects with suitable architecture settings for Universal products
My team uses CMake to build a cross platform application bundle consisting of several executables and shared libraries. Our target platforms are Windows x86, Windows x86_64, macOS x86_64 and macOS arm64. For Mac targets we use the Xcode generator and the xcodebuild tool to build an "archive" and export valid, signed products. When arm64 Macs were released by Apple and we needed to start building a "universal" (fat binary) application for macOS we ran into issues with CMake.
The Xcode projects generated by CMake with default settings do not produce fat binaries when built on either x86_64 or arm64 hosts. I attached projects that show an example of this. Examine "build output.txt" to find that for an x86_64 host it builds an x86_64-only binary and for an arm64 host, it builds an arm64-only binary. test-default-arch-settings-arm64_host.zip test-default-arch-settings-x86_host.zip
The CMake documentation describes CMAKE_OSX_ARCHITECTURES
and we tried setting this at configure time. See sample project. This setting has a couple undesirable outcomes. One is that the Xcode setting ONLY_ACTIVE_ARCH
gets set to NO
for every single individual target in the product. This means that even when our toolchain is building in Debug configuration for testing purposes, the build time is effectively twice as long because it builds an architecture that is not needed. The other issue is that because the ARCHS
and ONLY_ACTIVE_ARCH
Xcode settings are changed at every individual target level, it's not possible to override them with a global project setting.
test-recommended-arch-settings-x86_host.zip
We determined that it was possible to set Xcode global project settings with CMake variables to achieve the highly desired outcome of Debug configurations building only for host architecture and Release configurations building all supported architectures. Xcode itself uses macros to define which architectures make sense for a given platform:
set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=Debug] "$(NATIVE_ARCH_ACTUAL)")
set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=MinSizeRel] "$(ARCHS_STANDARD)")
set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=RelWithDebInfo] "$(ARCHS_STANDARD)")
set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=Release] "$(ARCHS_STANDARD)")
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
This combination of options works sufficiently for an x86_64 host. The Xcode project has host-only arch enabled for debug and all supported archs defined for release. It also only has arch settings defined for the global project level and not individual target level, so targets can have their arch settings overridden more easily when needed. The "build output.txt" shows that an archive build produces a fat binary. test-overriden-arch-settings-x86_host.zip
However these settings do not work on arm64 hosts for reasons that are not clear. If you examine the attached generated project, both the global and individual target settings have been overridden to "arm64" for all configurations. It appears that CMake is completely ignoring the CMAKE_XCODE_ATTRIBUTE_ARCHS
values which I have to assume is a defect.
test-overriden-arch-settings-arm64_host.zip
Overall, to a Mac-native developer like me, CMake's multi-arch support on macOS seems to be a mess. If you use Xcode to create a project it defaults to some very sensible settings. At the global project level, ARCHS = $(ARCHS_STANDARD)
and ONLY_ACTIVE_ARCH
is YES
for Debug and NO
for Release. Out of the box, target settings inherit from the project. I could not find any way to replicate this setup in CMake. In particular, it's very frustrating that changing the CMAKE_OSX_ARCHITECTURES
settings makes it apply changes to every single individual target. In a project where some targets need to only target a single architecture and where Debug should always only build the host architecture, the CMake options for Xcode target architecture are unhelpful at best.
I really think the best solution is for CMake to by default produce an Xcode project that is set up similarly to how Xcode itself creates projects. That means at the global level, the ARCHS
value is not set at all and ONLY_ACTIVE_ARCH
is YES
for Debug and NO
for Release. At the target level, either of those settings should only exist if there was an explicit directive in the CMake target via set_target_properties
for example. If you look at a freshly created Xcode pbxproj file there are very few individual target and even global project settings explicitly set other than warnings flags, because the defaults make sense.