CMAKE plugin and OS 11 Universal Binary

We want to build our first Universal Binary 2 for OS 11 ARM. We are using the new JCUE 6 CMAKE infrastructure and i started to think how we can build the plugins as Universal Binary without time consuming manual steps.

There are some problems i see:

  • The deployment target may has to be different for the ARM and Intel builds.
  • We may can’t build the AAX plugin because the library does not compile for ARM and is not supported?
  • Other problems that may occur?

Can this be configured within the CMAKE file and does juce already offer a way to build universal binaries 2 with CMAKE where we can keep our low deployment targets for the intel builds and also don’t have to care about AAX (maybe it can be excluded for this architecture as long as it’s not supported)?

Any help welcome!

I’ve successfully built universal JUCE targets by configuring the CMake project with

cmake -D CMAKE_OSX_ARCHITECTURES="arm64;x86_64"

To disable arm builds for specific targets, you should be able to use the OSX_ARCHITECTURES property:

set_target_properties(my_plugin_AAX PROPERTIES OSX_ARCHITECTURES x86_64)
4 Likes

Thanks for the fast answer. That looks pretty easy. I will try it asap. For the moment it looks like it was a good decision to switch to CMAKE builds :slight_smile:

Sorry, could you explain more in detail how to do that? I’m using CMAKE on an Intel Macbook and I’m compiling with the CMAKE extensions of Visual Studio Code. Basically my question is: how can I add that compilation flag inside the CMakeList.txt?

Add this to the project CMakeLists.txt somewhere before juce_add_*

if (APPLE)
    set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
endif()
1 Like

Awesome! thank you!

I was having some issues with my build server not producing a universal binary. I also wanted to support users on OSX 10.9.

First I tried exporting an Xcode project after adding these lines to my CMakeLists.txt:

if(APPLE)
    set (CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version" FORCE)
    set (CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Architectures" FORCE)
    set (CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=arm64] "11.0" CACHE STRING "arm 64 minimum deployment target" FORCE)
endif()

This worked, producing different minimum targets for each architecture. I then tried exporting to Ninja - this works even better as it integrates nicely into CMake and detects the number of available processors for parallel builds.

1 Like

Interesting, I didn’t know setting individual deployment targets for each architecture slice works. And I would expect that CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET is ignored when using Ninja, as it’s an Xcode specific attribute.

In any case, we successfully only set a single deployment target for universal binary builds which seems to work fine in general, even if the target predates 11.0. I don’t quite see the benefit of using a different deployment target for the ARM slice since the shared code cannot use any features introduced in later macOS versions anyway. Am I overlooking something here?

1 Like

I think despite this called “XCODE…” this is a global mac setting, I’m using those xcode attributes with Ninja and Unix Makefiles successfully.

Yeah, that works for me as well. I set 10.9 as the minimum and it ‘does the right thing’ on ARM.

1 Like

According to the docs, these variables only have an effect on the Xcode generator. It sounds like these settings are ignored in all other generators.

https://cmake.org/cmake/help/latest/variable/CMAKE_XCODE_ATTRIBUTE_an-attribute.html

Yes, I’ve read that too, but the docs are wrong.
I set those constantly on all my different computers and CI and they’re always accepted on all Mac generators that I’ve used.

My point is that it’s a bad idea to rely on behaviour that contradicts the documentation. Failing to ignore these variables in non-Xcode generators is arguably a bug in CMake, and this might be fixed in the future.

Actually I can’t imagine this would even change - without that feature the other Mac generators have no way of doing code sign, OS target, etc, so breaking that would make any other generator completely useless on Mac.

It would probably only change if the syntax of the call itself will modernize to not explicitly mention xcode.

  • CMAKE_OSX_ARCHITECTURES for universal builds
  • CMAKE_OSX_DEPLOYMENT_TARGET for deployment target

Note that these are not CMAKE_XCODE_ATTRIBUTE variables, will work for generators other than Xcode, and will work for targets other than macOS (iOS, watchOS etc.).

2 Likes

Ah yes, my mistake. You’re absolutely right.

I’m pulling my hair out over this: Has anyone managed to create juce universal-binary-2 XCode projects using CMake that only build the native architecture (arm64 in my case) for the Debug scheme, but are universal-2 for Release? I tried all kinds of stuff with [variant=…] and various cmake flags but only end up with a non-working mess or the Debug builds always being universal and taking twice as long as necessary.
Using google I also found that this has been a problem with CMake in the past and maybe it’s still unsolved? Any help would be very welcome.

this almost worked, but only affects the main “project”, not the single targets:

   set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=Debug] "$(NATIVE_ARCH_ACTUAL)")
   set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=MinSizeRel] "arm64;x86_64")
   set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=RelWithDebInfo] "arm64;x86_64")
   set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=Release] "arm64;x86_64")

I would do this using generator expressions to set target properties:

function(set_univ_bin target)

    if(NOT APPLE)
        return()
    endif()

    execute_process (COMMAND uname -m OUTPUT_VARIABLE osx_native_arch
                     OUTPUT_STRIP_TRAILING_WHITESPACE)

    message (VERBOSE "Mac native arch: ${osx_native_arch}")

    get_property (debug_configs GLOBAL PROPERTY DEBUG_CONFIGURATIONS)

    if (NOT debug_configs)
        set (debug_configs Debug)
    endif ()

    set (config_is_debug "$<IN_LIST:$<CONFIG>,${debug_configs}>")
    set (config_is_release "$<NOT:${config_is_debug}>")

    set_target_properties ("${target}" PROPERTIES 
        OSX_ARCHITECTURES
        "$<${config_is_debug}:${osx_native_arch}>"
        "$<${config_is_release}:x86_64$<SEMICOLON>arm64>"
        )
endfunction()

Thank you for the help!
Unfortunately, I couldn’t make your script work. It seems generator expressions are not supported inside set_target_properties().

Luckily, playing around with your code led me to a solution that works for me for now. I realized that CMake by default sets the architectures to “arm64”, so all I need is to make the Release config/variant universal on both the project and all targets as unfortunately the project setting is overridden for each target by CMake.

On top of my CMakeLists.txt I configure the project:

if (APPLE)   
    set(CMAKE_XCODE_ATTRIBUTE_ARCHS[variant=Release] "$(STANDARD_ARCHS)")

After setting up the targets I iterate through the formats and some extra targets (this is a juce plugin build):

if (APPLE)
    foreach(t ${FORMATS} "BinaryData" "All" "")
        set(tgt ${CMAKE_PROJECT_NAME})
        if(NOT t STREQUAL "") 
            set(tgt ${tgt}_${t})
        endif()
        if(TARGET ${tgt})
            set_target_properties(${tgt} PROPERTIES 
                XCODE_ATTRIBUTE_ARCHS[variant=Release] "$(STANDARD_ARCHS)"
            )
        endif()
    endforeach()
endif()

That sets everything the way I need it for now (still using Xcode 13.2 on 12.3)

You probably don’t want to globally build universal binary for release builds, as you also probably test release builds locally, and the 2x built times are just a waste of your dev time. Also, scripting that would create complication in your build script that isn’t gonna be easy to get rid of when you don’t want it.

Instead, just pass the universal binary argument to CMake when you’re actually deploying.
Something like:

#dev build:
cmake -G Xcode -B build

#deployment build:
cmake -G Xcode -B build-deploy -D CMAKE_OSX_ARCHITECTURES "x86_64;arm64"

Edit: Also universal binaries are useful for debug builds too, as sometimes you need to test on Rosetta-based hosts, so that’s another reason to be able to manually enable universal builds/intel only arch when you need it, and not have too much deployment logic in your build script.

I certainly want it, but it’s of course a matter of taste and workflow. To each his own.
What I wanted is the default way XCode works without CMake and I frankly find it quite weird this isn’t much easier to achieve. I use Debug builds for development and Release builds for testing on multiple machines and sometimes making sure the other arch still builds correctly, so this makes perfect sense to me.