CMake: how to change compiler for individual target

I just had the same issue right now, but the other answer didn’t help me. I’m also cross-compiling, and I need some utility programs to be compiled with GCC, but my core code to be compiled with avr-gcc.

Basically, if you have a CMakeLists.txt, and you want all targets in this file to be compiled with another compiler, you can just set the variables by hand.

Define these macros somewhere:

macro(use_host_compiler)
  if (${CURRENT_COMPILER} STREQUAL "NATIVE")
    # Save current native flags
    set(NATIVE_C_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "GCC flags for the native compiler." FORCE)

    # Change compiler
    set(CMAKE_SYSTEM_NAME ${CMAKE_HOST_SYSTEM_NAME})
    set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR})
    set(CMAKE_C_COMPILER ${HOST_C_COMPILER})
    set(CMAKE_C_FLAGS ${HOST_C_FLAGS})
    set(CURRENT_COMPILER "HOST" CACHE STRING "Which compiler we are using." FORCE)
  endif()
endmacro()


macro(use_native_compiler)
  if (CMAKE_CROSSCOMPILING AND ${CURRENT_COMPILER} STREQUAL "HOST")
    # Save current host flags
    set(HOST_C_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "GCC flags for the host compiler." FORCE)

    # Change compiler
    set(CMAKE_SYSTEM_NAME ${NATIVE_SYSTEM_NAME})
    set(CMAKE_SYSTEM_PROCESSOR ${NATIVE_SYSTEM_PROCESSOR})
    set(CMAKE_C_COMPILER ${NATIVE_C_COMPILER})
    set(CMAKE_C_FLAGS ${NATIVE_C_FLAGS})
    set(CURRENT_COMPILER "NATIVE" CACHE STRING "Which compiler we are using." FORCE)
  endif()
endmacro()

At the very beginning of your CMakeLists.txt script (or in a toolchain file), set the following variables according to what you need:

  • CURRENT_COMPILER
  • HOST_C_COMPILER
  • HOST_C_FLAGS
  • NATIVE_SYSTEM_NAME
  • NATIVE_C_COMPILER
  • NATIVE_C_FLAGS

The idea is that CMAKE_C_COMPILER (and company) is a variable like any other, so setting it inside a certain scope will only leave it changed within that scope.

Caveat: Changing CMAKE_C_COMPILER is a “hack” and isn’t officially supported by CMake. That’s because the CMake documentation on the compiler variable says “Once set, you can not change this variable”. However, @ChrisB found out that, in practice and with the current versions of CMake (~3.25), this hack only works when all the targets in the same directory use the same compiler, at least when using CMake’s Unix Makefiles generator. This means, if you want to have targets that use different compilers, these targets need to be in different subdirectories, using add_subdirectory. See @ChrisB’s answer. I wouldn’t be surprised if this hack works only with some CMake Generators and fails with others (say, works with Unix Makefiles and Ninja but not with Visual Studio 17 2022 or something like that).


Example usage:

# src/host/CMakeLists.txt
use_host_compiler()
add_executable(foo foo.c) # Compiled with your host (computer)'s compiler.

# src/native/CMakeLists.txt
use_native_compiler()
add_executable(bar bar.c) # Compiled with your native compiler (e.g. `avr-gcc`).

NOTE: Another approach, which requires more work but which is guaranteed to work well no matter what, and is good practice for projects of all sizes, is to call CMake multiple times on the project, once for each compiler, for example using a toolchain file to specify the compiler and flags for each build. That doesn’t necessarily mean duplicating the CMake project and scripts. You can just ensure that your CMake build builds only the targets relevant for the current compiler.

For example, if you need to build a code generator that runs on the host platform (e.g. x64) that will generate code for an embedded platform (like was the case for me back then), then first invoke CMake with the host toolchain file (e.g. the x64 one), making sure that only the code generator gets built. Then, once that CMake build completes, make another CMake build in a new build directory with the embedded platform toolchain file (e.g. the one that uses avr-gcc), importing the code generator of the host platform that you just built. You can then write something like a shell script that makes both builds for you, unless you prefer simply writing the commands for building both of them in your project’s README.md.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)