31

I have embedded project using cross compiler. I would like to introduce Google test, compiled with native GCC compiler. Additionally build some unit test targets with CTC compiler.

Briefly:
I have 3 different targets and compile them with 3 different compilers. How to express it in CMakeLists.txt? I Tried SET_TARGET_PROPERTIES;
but it seems impossible to set CXX variable with this command!

Andrew123
  • 437
  • 1
  • 4
  • 5

5 Answers5

15

NOTE Some have reported that this doesn't work for them. It definitely used to work for the project I was working on at the time, which was using CMake's Makefile generator on Ubuntu. It's possible this technique (hack?) only works with CMake's Makefile generator, so if you're using Ninja or another generator, that might be why it's not working.


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.


Example usage:

use_host_compiler()
add_executable(foo foo.c) # Compiled with your host (computer)'s compiler.
use_native_compiler()
add_executable(bar bar.c) # Compiled with your native compiler (e.g. `avr-gcc`).
adentinger
  • 1,367
  • 1
  • 14
  • 30
  • 1
    I'm in the same boat where I need different compilers for different targets. I've tried your example, but cannot get this to work. The added executable always is built with clang instead of my alternative compiler. Any suggestions to make this work? – acronce Apr 29 '19 at 18:59
  • It's difficult to say when you don't have access to the code, but you can try printing out the variables' values with `message()` just before calling `add_executable` (or `add_library` or whatever you are using) to see if they are still set according to your needs at that moment. Also make sure you use `macro()` and _not_ `function()`, since setting a variable within a function would leave it unchanged in the caller's scope, whereas a variable that is set within a `macro()` stays at that value in the caller scope. – adentinger Apr 29 '19 at 19:59
  • Thanks for the reply. Yes, I did this in a macro not a function. And I already tried printing out the the variable values. Here's the output just before defining the executable -- Switching compiler to TEST... -- CMAKE_SYSTEM_NAME: TEST_SYSTEM -- CMAKE_SYSTEM_PROCESSOR: -- CMAKE_C_COMPILER: /home/me/scripts/clang_wrapper.py -- CMAKE_CXX_COMPILER: /home/me/scripts/clang_wrapper++.py -- CMAKE_C_FLAGS: -- CMAKE_CXX_FLAGS: -- CURRENT_COMPILER: TEST – acronce Apr 29 '19 at 23:04
  • I'm not familiar with this `clang_wrapper.py` of yours. It's possible that `clang_wrapper.py` does not accept the same kind of arguments as gcc/g++, which would cause CMake to call it with gcc's arguments, like `-o outputfile.o -c inputfile.c`, which may be invalid arguments to `clang_wrapper.py`. – adentinger Apr 30 '19 at 01:07
  • Our wrapper script was designed to handle clang/gcc arguments. There's no indication that that is failing. What it seems like is it just isn't possible to have N compilers defined for the same CMake configured build. I can't find any way to do this without multiple config passes, specifying the compiler and a different build dir for each pass. I've decided to turn the problem around, and build all of our targets with our wrapper, where for most targets it just calls through to clang, and in other cases it does the special stuff that we need to do. That works around the problem for me. – acronce May 01 '19 at 00:44
  • Which CMake version did you use successfully with this code? – Daniel Jour Oct 01 '19 at 08:48
  • @DanielJour It was the up-to-date version of two years ago. I might have read somewhere that you couldn't do that anymore. We used this CMake trick as part of this project: https://github.com/MISTLab/BittyBuzz/blob/5af765d248731f35365a1c5f6193553efcc25fe0/src/cmake/BittyBuzzCompilerMacros.cmake . If you look at the root `CMakeLists.txt` file under `src/`, it seems we required CMake version 3.3.2 . – adentinger Oct 01 '19 at 13:59
  • 1
    I can also confirm that this does not (/no longer?) work. Tested on cmake 3.25, running on debian. – ChrisB Mar 18 '23 at 17:29
  • @ChrisB It's possible that that technique (hack?) works only with CMake's `Makefile` generator, which is what I was using at the time. But it definitely used to work. I was on Ubuntu. I added a note at the top of the answer to explain this. – adentinger Mar 24 '23 at 14:31
  • 1
    @adentinger: I think the confusion here comes from your example at the bottom: `use_host_compiler() add_executable(...) use_native_compiler() add_executable(...)` **In the same file**, as the example kind of implies, will not work, and presumably never did. The reason this works (also in your repo) is `add_subdirectory`, which I point out in [my answer](https://stackoverflow.com/a/75777519/7204912). Admittedly, you do mention at the top that this sets it for *all targets in this file*, so this answer is technically correct, while being a bit confusing (apparently not just to me :)). – ChrisB Mar 26 '23 at 16:06
6

There is no proper way to change compiler for individual target.

According to cmake manual "Once set, you can not change this variable". This is about CMAKE_<LANG>_COMPILER.

The solution suggested by AnthonyD973 does not seem to work, which is sad of course. The ability to use several compilers in a project without custom_command things is very useful.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
4

The canonical way to achieve this is to introduce separate scopes using add_subdirectory, and then set the compiler for the scope using e.g. set(CMAKE_C_COMPILER gcc) or set(CMAKE_CXX_COMPILER clang++). A simple example project layout:

project
├── foo
│   └── CMakeLists.txt
├── bar
│   └── CMakeLists.txt
├── CMakeLists.txt
└── main.c
# ./project/CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(demo)
add_subdirectory(./foo)
add_subdirectory(./bar)
# ./project/foo/CMakeLists.txt:
set(CMAKE_C_COMPILER clang)
add_executable(foo ../main.c)
# ./project/bar/CMakeLists.txt:
set(CMAKE_C_COMPILER gcc)
add_executable(bar ../main.c)

Shared variables can be set in the parent CMakeLists.txt, as they are inherited.

Ideally, these targets somewhat correspond to your project's actual directory layout, so you don't need to add a bunch of dummy folders.

ChrisB
  • 1,540
  • 6
  • 20
2

One solution (that I haven't tried yet) is to use

set_target_properties(your_target CXX_COMPILER_LAUNCHER foo_wrapper)

Then make foo_wrapper a script that just drops the first argument (which will be the default compiler, e.g. c++) and then calls the compiler you want.

There's also CXX_LINKER_LAUNCHER and the same for C_....

Timmmm
  • 88,195
  • 71
  • 364
  • 509
-9

CMake is a make file generator. It generates a file that you can then use to build. If you want to more than one target platform, you need to run CMake multiple times with different generators.

So what you want to do is not possible in CMake, but with CMake: You can create a shell script that invokes CMake multiple times.

Andreas Haferburg
  • 5,189
  • 3
  • 37
  • 63
  • I tend to agree with this, but you could create custom_target which invokes cmake with some other toolchain file, build and install it to some intermediate stage dir, export-import executables... Although probably more difficult then external script, this may look nicer from outside. – Slava Mar 22 '18 at 10:41