Reputation: 477
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!
Upvotes: 35
Views: 21605
Reputation: 1404
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()
# Compiled with your host (computer)'s compiler.
add_executable(foo foo.c)
######
# In a *different* CMakeLists.txt, e.g. src/native/CMakeLists.txt
use_native_compiler()
# Compiled with your native compiler (e.g. `avr-gcc`).
add_executable(bar bar.c)
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, or simply by setting -DCMAKE_CXX_COMPILER=<value>
and friends on the command-line 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 x64 that will generate code for an embedded platform (like was the case for me back then), then first run a CMake build with the x64 variables, making sure that only the code generator gets built, then run another CMake build with the embedded platform variables (e.g. specifying avr-g++ as the CMAKE_CXX_COMPILER
), importing the code generator of the host platform that you just built. You can then write something like a shell script or a python script like this one I made that runs both builds for you, unless you prefer simply writing the commands for building both of them in your project's README.md
.
This requires somehow "exporting" the host executable in the first build and "importing" it in the second. The "easy" way of doing that is to just hardcode the path to the executable within the second build and just invoke it.
Instead of hardcoding the path though, there is a more robust way of exporting the CMake targets, but which involves a bit of figuring out if you've never done it before. It is explained in this tutorial, and you can see that in a more "real" context in this file of mine, at lines 34 to 80.
Once you've figured out exporting the first build, importing that in the second one is a matter of:
-DCMAKE_PREFIX_PATH=<path>
, where <path>
is the path where cmake --install <build-dir>
installed the first build, a.k.a. the "install prefix".find_package(<pkg> CONFIG REQUIRED)
, where <pkg>
is the name of the package you've exported the first build with. E.g. if the config-file is mypkg1Config.cmake
, <pkg>
would be mypkg1
.Once that is done, the mypkg1::<exe-taget>
(where <exe-target>
is the name of the executable target in the first build, e.g. host-gen
) is automatically created with all the correct paths and properties, so you can obtain the path to the executable using the $<TARGET_FILE:mypkg1::host-gen>
generator expression.
Even though that second method is more involved, its advantages are:
target_link_libraries(mylib PUBLIC mypkg1::lib1)
and CMake takes care of figuring out what mylib
needs from mypkg1::lib1
.Upvotes: 15
Reputation: 3714
The compiler can be set through the CMAKE_C_COMPILER
variable (CMAKE_CXX_COMPILER
if you're using c++).
CMake only checks the value of this variable once, after the full build script has finished. This means that if you set the variable multiple times, only the last value that was set actually matters. (Note: according to the docs, setting this variable multiple times is not officially supported).
If we want to use multiple compilers, we need to introduce multiple separate scopes (namespaces) using add_subdirectory
. We can then set the CMAKE_C_COMPILER
variable to different values in each of these scopes. Here's 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)
Variables set in the parent CMakeLists.txt
before the call to add_subdirectory
, will be inherited by the subscopes.
Ideally, these scopes somewhat correspond to your project's actual directory layout, so you don't need to add a bunch of dummy folders.
Upvotes: 6
Reputation: 96537
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_...
.
Upvotes: 1
Reputation: 99
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.
Upvotes: 7
Reputation: 5510
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.
Upvotes: -9