How to determine which architectures are available (Apple M1)?

I have 2 different machines: an “old” Intel mac and a new M1 mac.

On the new (M1) mac I can write this to build a universal binary:

IF(APPLE)
  SET(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Build architectures for Mac OS X" FORCE)
ENDIF(APPLE)

But I cannot do that on an Intel mac (it is running an older version of macOS hence an older version of Xcode which does not support arm64 anyway). Is there a way to get the list of supported architectures by the tool chain?

Thanks

2 Likes

If I set CMAKE_OSX_ARCHITECTURES to x86_64;arm64 with XCode 11.3.1 (macos 10.14.6) I confirm that it just doesn’t work:

CMake Error at /Applications/CLion.app/Contents/bin/cmake/mac/share/cmake-3.17/Modules/CMakeTestCCompiler.cmake:60 (message):
  The C compiler

    "/Applications/Xcode11.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc"

  is not able to compile a simple test program.

  It fails with the following output:

    Change Dir: /Volumes/Vault/deployment/build/vst-sam-spl-64/Debug/CMakeFiles/CMakeTmp
    
    Run Build Command(s):/usr/bin/make cmTC_7d8db/fast && /Applications/Xcode11.3.1.app/Contents/Developer/usr/bin/make  -f CMakeFiles/cmTC_7d8db.dir/build.make CMakeFiles/cmTC_7d8db.dir/build
    Building C object CMakeFiles/cmTC_7d8db.dir/testCCompiler.c.o
    /Applications/Xcode11.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc   -arch x86_64 -arch arm64 -isysroot /Applications/Xcode11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -mmacosx-version-min=10.14   -o CMakeFiles/cmTC_7d8db.dir/testCCompiler.c.o   -c /Volumes/Vault/deployment/build/vst-sam-spl-64/Debug/CMakeFiles/CMakeTmp/testCCompiler.c
    Linking C executable cmTC_7d8db
    /Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake -E cmake_link_script CMakeFiles/cmTC_7d8db.dir/link.txt --verbose=1
    /Applications/Xcode11.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc   -arch x86_64 -arch arm64 -isysroot /Applications/Xcode11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -mmacosx-version-min=10.14 -Wl,-search_paths_first -Wl,-headerpad_max_install_names   CMakeFiles/cmTC_7d8db.dir/testCCompiler.c.o  -o cmTC_7d8db 
    ld: warning: ignoring file /Applications/Xcode11.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.osx.a, missing required architecture arm64 in file /Applications/Xcode11.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.osx.a (3 slices)
    ld: warning: ignoring file /Applications/Xcode11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libSystem.tbd, missing required architecture arm64 in file /Applications/Xcode11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libSystem.tbd
    ld: dynamic main executables must link with libSystem.dylib for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    make[1]: *** [cmTC_7d8db] Error 1
    make: *** [cmTC_7d8db/fast] Error 2

So any idea of how I can test for architecture support?

Thanks

This works for me:

cmake_minimum_required(VERSION 3.19)
project(Test)
include(CheckCCompilerFlag)

set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;x86_64")
check_c_compiler_flag("-arch x86_64" x86_64Supported)
message("x86_64Supported=${x86_64Supported}")

set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;arm64")
check_c_compiler_flag("-arch arm64" arm64Supported)
message("arm64Supported=${arm64Supported}")

On Xcode 12.3 it indicates both x86_64 and arm64 are supported; on Xcode 8.2.1 it indicates only x86_64 is supported.

1 Like

I guess it is a chicken and egg problem…

The CMAKE_OSX_ARCHITECTURES variable is supposed to be set before calling project but it looks like check_c_compiler_flag needs to be called after project

CMake Error at /Applications/CLion.app/Contents/bin/cmake/mac/share/cmake-3.17/Modules/CheckCSourceCompiles.cmake:109 (try_compile):
  Unknown extension ".c" for file

    /Volumes/Vault/deployment/build/vst-sam-spl-64/Debug/CMakeFiles/CMakeTmp/src.c

  try_compile() works only for enabled languages.  Currently these are:

    

  See project() command to enable other languages.

It seems to me that I am going to have to invoke some command line program (like sysctl -a | grep machdep.cpu.brand_string) via execute_process to determine the processor :frowning:

@fry I’ve posted a related question asking for assistance testing an approach which may help you in producing universal binaries. If you are able to give that test project a go, it would be much appreciated.

@craig.scott No problem I will try it and report back

Ah, I see. Yeah, check_c_compiler_flag won’t work then.

(There’s also the arch command whose output might be easier to parse.)

One thing to watch out for is that the system CPU architecture doesn’t necessarily match the architectures that Xcode can compile for, since Xcode 12 running on x86_64 can cross-compile for arm64. I’m not aware of a straightforward way to query which architectures a given Xcode.app bundle can cross-compile for, but it’s easy to test:

file(WRITE test.c "int main(void) { return 0; }")
execute_process(COMMAND /usr/bin/xcrun clang -arch arm64 test.c ERROR_QUIET RESULT_VARIABLE arm64Error)
if (arm64Error)
    message("cross-compiling for arm64 isn't supported")
else()
    message("cross-compiling for arm64 is supported")
endif()

On Xcode 12, the test compile and link both succeed. On earlier Xcode versions, it compiles (since earlier Xcode versions include arm64 support for iOS devices), but linking fails since the macOS libSystem doesn’t include an arm64 slice.

(This has the caveat that, if there are multiple toolchains installed on the system, xcrun/xcode-select might not use the same one that CMake’s project()/enable_language() will later pick.)

@smokris Thank you for the help. I agree with you and the caveat.

The command uname -m returns the exact architecture of the machine (x86_64 or arm64) but you are correct that it doesn’t say whether the compiler will allow cross compilation.

Since it looks like everybody compiling in the near future for macOS will be faced with the same kind of issue, I am hoping that this can be a part of CMake. Maybe a new variable like XCODE_UNIVERSAL_BUILD_WHEN_AVAILABLE which would do the right thing whether on M1 (universal build) or Intel (single build)?

1 Like

I don’t think a new variable is needed. It looks like you can set CMAKE_OSX_ARCHITECTURES to $(ARCHS_STANDARD) if you want a universal build where available for macOS. CMake’s default behavior has always been to build for the native host architecture only. There was a fair amount of investigation and work that went into keeping that behavior with Xcode 12 and Apple Silicon to avoid various problems. See issue 20893 for the fairly detailed discussions and related information.

Depending on what version of macOS and Xcode you are running, you may also need to set CMAKE_OSX_DEPLOYMENT_TARGET to be able to run your built apps. I have the situation locally where I build with Xcode 12.3 on an intel running macOS 10.15. If I don’t specify CMAKE_OSX_DEPLOYMENT_TARGET, I end up building a valid binary that I can’t run on my machine.

I don’t think I’ve seen that myself. When you say “multiple toolchains”, do you mean multiple Xcode versions installed at once, or different SDKs within the one Xcode version? If the latter, do you mean just having different architectures’ SDKs installed? Xcode will usually have the different architectures SDKs, so the latter case is the normal one.

CMake has always picked up the right toolchain for me, and I have multiple Xcode versions installed too. I switch between them either by changing the default one (using xcode-select or within the Xcode IDE preferences) or by setting the DEVELOPER_DIR environment variable when running CMake for the first time in a build directory.

2 Likes

I was thinking of non-Xcode toolchains (e.g., LLVM/Clang installed via Conan that xcode-select doesn’t know about), selected by setting CMAKE_<LANG>_COMPILER.

I wanted to report back on how I ended up implementing it. I am not saying it is the best way or the only way to do it, but it is the way that makes the most sense for my project/framework. I know that a solution proposed in another thread (and in the 8th edition of the book) is to use $(ARCHS_STANDARD) which gets expanded by Xcode, but I don’t like it because the content of this variable is not available in CMake so I can’t act on it (printing a message, generating the zip archive name, etc…) (see below).

The full source is available on github.

The gist of it:

  • allow to disable universal build if you don’t want it via an option
  • run uname -m to determine the platform hence whether universal build is even an option
  • check whether the generator is Xcode otherwise you can’t do a universal build
  • I also set a variable (JAMBA_ARCHIVE_ARCHITECTURE) representing the build architecture so that it is available in other places for example to determine the name of the zip file generated and also injected in a header in the code (#define). This would be impossible to do with $(ARCHS_STANDARD)
# Enable/Disable universal build on Apple Silicon
option(JAMBA_ENABLE_XCODE_UNIVERSAL_BUILD "Enable building x86/arm64 universal binary on Apple Silicon" ON)

# Deployment target/architectures for macOS
if(APPLE)
  # set the deployment target if provided
  if(JAMBA_MACOS_DEPLOYMENT_TARGET)
    set(CMAKE_OSX_DEPLOYMENT_TARGET "${JAMBA_MACOS_DEPLOYMENT_TARGET}" CACHE STRING "")
  endif()

  # on macOS "uname -m" returns the architecture (x86_64 or arm64)
  execute_process(
      COMMAND uname -m
      RESULT_VARIABLE result
      OUTPUT_VARIABLE JAMBA_OSX_NATIVE_ARCHITECTURE
      OUTPUT_STRIP_TRAILING_WHITESPACE
  )

  # determine if we do universal build or native build
  if(JAMBA_ENABLE_XCODE_UNIVERSAL_BUILD                     # is universal build enabled?
      AND (CMAKE_GENERATOR STREQUAL "Xcode")                # works only with Xcode
      AND (JAMBA_OSX_NATIVE_ARCHITECTURE STREQUAL "arm64")) # and only when running on arm64
    set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "")
    set(JAMBA_ARCHIVE_ARCHITECTURE "macOS_universal")
    message(STATUS "macOS universal (x86_64 / arm64) build")
  else()
    set(JAMBA_ARCHIVE_ARCHITECTURE "macOS_${JAMBA_OSX_NATIVE_ARCHITECTURE}")
    message(STATUS "macOS native ${JAMBA_OSX_NATIVE_ARCHITECTURE} build")
  endif()

elseif(WIN32)
  # nothing special for windows
  set(JAMBA_ARCHIVE_ARCHITECTURE "win_64bits")
  message(STATUS "Windows native build")
endif()```
1 Like

I simplified your answer and hope you like it.

if(APPLE)
execute_process(COMMAND uname -m
OUTPUT_VARIABLE CMAKE_OSX_ARCHITECTURES
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif(APPLE)

Except that your answer is wrong. If you look at what I am actually doing, I am building a universal application.

uname -m only returns arm64 on an M1 so setting CMAKE_OSX_ARCHITECTURES to the result of uname -m will build an M1 only application.

This is why I am using set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING ""). But on top of that, it only works if the generator is Xcode.

So no, your “simplification” does NOT do what my code does and is certainly NOT setting CMAKE_OSX_ARCHITECTURES to x86_64;arm64 as the original question was asking…