Check CMake Cache Variable in Toolchain File - cmake

I'm having trouble setting a configuration variable via the command line. I can't determine it from the system, so I expect the user to specify:
cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain -DANDROID_ABI:STRING="arm64" ..
Inside my android.toolchain, I have the following:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
No matter what, it fails at this line EVEN THOUGH it prints out the correct arm64:
-- Android ABI: arm64
CMake Error at yaml-cpp/android.toolchain:45 (message):
Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64
Could anyone direct me to what I'm doing wrong?
I think this has to do with:
-D adds a cache variable instead of a normal variable
This is in a toolchain file... it seems to ignore cache variables
Any thoughts or suggestions?

I don't pretend to fully understand what's going on behind the scenes, but here's a workaround that works for me:
# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.
# Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.
if (FOO)
# Environment variables are always preserved.
set(ENV{_FOO} "${FOO}")
else ()
set(FOO "$ENV{_FOO}")
endif ()

CMake 3.6 introduces variable CMAKE_TRY_COMPILE_PLATFORM_VARIABLES which contains a list of variables, automatically passed from the main project to the project, created with try_compile.
A toolchain may add its variables to that list, so they could be extracted in a subproject:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
# propagate the variable into "inner" subprojects.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "ANDROID_ABI")
Caveats:
This approach affects only to source flow of try_compile command. It won't work when try_compile is used for create fully-fledged CMake project with signature
try_compile(<resultVar> <bindir> <srcdir> <projectName> ...)
(Approach with setting environment variable, as described in the #sorbet answer, works perfectly in this case.)
This approach won't work for a subproject, created with ExternalProject_Add.
(Approach with setting environment variable fails in that case too.)

Related

build cmake subproject with differents toolchain [duplicate]

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!
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`).
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.
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_....
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.

Prevent cmake from allowing any value for CMAKE_CONFIGURATION_TYPES

I am trying to do something like
cmake -DCMAKE_CONFIGURATION_TYPES="Debug;RelWithDebInfo" ...
I wanted to check if I did not have any mistakes in type names so I tried something nonsensical hoping cmake will error out and confirm that my configuration types are correct.
cmake -DCMAKE_CONFIGURATION_TYPES="Debug;Relalaland" ...
But cmake just creates the configuration types I specify...
Is there a way to tell cmake to not accept unfamiliar CONFIGURATION_TYPES?
Is there a way to tell cmake to not accept unfamiliar CONFIGURATION_TYPES?
Don't. A user might want to define a custom configuration type externally, which is perfectly possible to do in a toolchain file or at the command line. One use for this might be for sanitizer builds. In general, you should avoid restricting what your users can do with your build. Only hard requirements go in your CMakeLists.txt and denying a custom build type doesn't qualify.
All that said, here's how you can do this:
set(known_types Debug Release RelWithDebInfo MinSizeRel)
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (is_multi_config)
foreach (config IN LISTS CMAKE_CONFIGURATION_TYPES)
if (NOT config IN_LIST known_types)
message(FATAL_ERROR "Build type '${config}' invalid. Expected one of: ${known_types}")
endif ()
endforeach ()
elseif (NOT CMAKE_BUILD_TYPE IN_LIST known_types)
message(FATAL_ERROR "Build type '${CMAKE_BUILD_TYPE}' invalid. Expected one of: ${known_types}")
endif ()
This works for both single and multi config generators.
So just check them.
set(allowed_types Debug Release RelWithDebInfo MinSizeRel)
foreach(i IN LISTS CMAKE_CONFIGURATION_TYPES)
if(NOT i IN_LIST allowed_types)
message(FATAL_ERROR "${i} is not valie configuration type")
endif()
endforeach()

Why is the toolchain file executed a few times in CMake?

In an attempt to create a cross-compilation CMake toolchain template with the SDCC compiler, I have come across a very weird issue.
As described in this link, if the toolchain.cmake file defines a CMAKE_SYSTEM_NAME, CMake will look for the file with the ${CMAKE_SYSTEM_NAME}.cmake under the Module/Platform directory. And this file should define platform-specific options. In my case, I am using it to find the sdcc compiler and setting some compiler flags.
This works just fine for me. Using cmake -DCMAKE_MODULE_PATH="${PATH_TO_MY_MODULES}" -DCMAKE_TOOLCHAIN_FILE="${PATH_TO_MY_TOOLCHAIN}" -DSDCC_SYSROOT="SOME_VALUE", CMake finds all the correct toolchain and platform files.
It seems like the toolchain and the platform file are executed (not sure if that's the correct term) a few times during the configuration process. In the first few times, the variable SDCC_SYSROOT I passed in the CMake command has the value SOME_VALUE as expected. However, the same variable SDCC_SYSROOT seems to lose the value in the last time these toolchain/platform files are executed. So they are empty. This causes my script to generate a fatal error.
toolchain.cmake has the following contents:
set(CMAKE_SYSTEM_NAME SDCC_PIC_16F877A)
# Finding resource settings
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# Set default MCU family and model
if (NOT MICROCHIP_FAMILY)
set(MICROCHIP_FAMILY "pic16")
endif()
if (MICROCHIP_MODEL STREQUAL "pic16")
set(MICROCHIP_MODEL "16f877a")
endif()
# Need a better way to detect the supported models here
if (NOT MICROCHIP_FAMILY STREQUAL "pic16" AND NOT MICROCHIP_MODEL STREQUAL "16f877a")
message(FATAL_ERROR "Settings not supported. Please drop a request.")
endif()
if (NOT SDCC_ROOT)
message(FATA_ERROR "Need to provide the root (from toolchain.)")
endif()
# Cache those variables
set(SDCC_ROOT "${SDCC_ROOT}"
CACHE INTERNAL "Root directory of SDCC installation")
set(MICROCHIP_FAMILY "${MICROCHIP_FAMILY}"
CACHE INTERNAL "Family of the chip to compile for")
set(MICROCHIP_MODEL "${MICROCHIP_MODEL}"
CACHE INTERNAL "Model of the chip to compile for")
the Module/Platform/SDCC_PIC_16F877A.cmake file has the contents:
# Check if the shit exists
message("!!! The value of root is ${SDCC_ROOT}")
if (NOT SDCC_ROOT)
message(FATAL_ERROR
"SDCC_ROOT is not defined. Please set this variable e.g.\n"
"cmake -DSDCC_ROOT=\"C:/Program Files/sdcc\"")
endif()
# Finding the compilers
find_program(CMAKE_C_COMPILER
sdcc
PATHS ${SDCC_ROOT}
PATH_SUFFIXES "bin"
DOC "path to the SDCC C compiler.")
and my CMakeLists.txt is the following:
cmake_minimum_required(VERSION 3.10)
project(PicExample)
message("THE COMPILER IS ${CMAKE_C_COMPILER}")
add_executable(pic_example main.c)
what I invoke from my project/build directory and the error I get:
cmake -DCMAKE_MODULE_PATH:FILEPATH="/mnt/c/Users/mathe/Desktop/coding/sdcc-pic-template/Modules" -DCMAKE_TOOLCHAIN_FILE:FILEPATH="/mnt/c/Users/mathe/Desktop/coding/sdcc-pic-template/Modules/toolchain.cmake" -DSDCC_ROOT="testing/" ..
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
!!! The value of root is testing/
!!! The value of root is testing/
-- Check for working C compiler: /usr/bin/cc
FATA_ERRORNeed to provide the root (from toolchain.)
!!! The value of root is
CMake Error at /mnt/c/Users/mathe/Desktop/coding/sdcc-pic-template/Modules/Platform/SDCC_PIC_16F877A.cmake:4 (message):
SDCC_ROOT is not defined. Please set this variable e.g.
cmake -DSDCC_ROOT="C:/Program Files/sdcc"
Call Stack (most recent call first):
/usr/share/cmake-3.16/Modules/CMakeSystemSpecificInformation.cmake:26 (include)
/mnt/c/Users/mathe/Desktop/coding/sdcc-pic-template/build/CMakeFiles/CMakeTmp/CMakeLists.txt:3 (project)
CMake Error at /usr/share/cmake-3.16/Modules/CMakeTestCCompiler.cmake:44 (try_compile):
Failed to configure test project build system.
Call Stack (most recent call first):
CMakeLists.txt:2 (project)
-- Configuring incomplete, errors occurred!
See also "/mnt/c/Users/mathe/Desktop/coding/sdcc-pic-template/build/CMakeFiles/CMakeOutput.log".
Why do the toolchain files get "executed" more than once by CMake and has no access to cache in the latest runs? I've been finding CMake documentation for cross-compilation very difficult, especially if you are working with a non-standard compiler.
I am aware that other people have had same issues before, but I am not simply asking for a simple hacky solution (setting environment variables). I actually want to know why this happens (which the previous answers don't tackle).
Tsyvarev answered the why the toolchain is used multiple times in CMake. TLDR; CMake needs it for multiple try_compile() calls it uses internally for error checking and other things.
This works just fine for me.
-DCMAKE_MODULE_PATH="${PATH_TO_MY_MODULES}" -DCMAKE_TOOLCHAIN_FILE="${PATH_TO_MY_TOOLCHAIN}" -DSDCC_SYSROOT="SOME_VALUE",
To fix your problem here is what you need to do.
Essentially you are passing an argument to your toolchain file. And this argument SDCC_SYSROOT essentially goes out of scope.
To fix this problem here is what you need to do.
# Use list(APPEND) rather than set() so that any variables added by CMake aren't lost!
#
# Here is the docs for this variable:
# https://cmake.org/cmake/help/latest/variable/CMAKE_TRY_COMPILE_PLATFORM_VARIABLES.html
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES ${SDCC_SYSROOT})
If you want to see how many times your toolchain script gets executed try putting in a message() call in there for fun.
And if you are really interested look inside your build folder and see what it is CMake is doing.
If you are wondering how I know this information it's because I read the toolchain section in Craig Scott's CMake book "Professional CMake:
A Practical Guide"
Here is a link: https://crascit.com/professional-cmake/
For determine, whether some feature is supported by the compiler or by some library, CMake uses try_compile approach: during the configuration phase, it creates separate CMake project and immediately configures and builds it. Because it is a separate project, its configuration has the same steps as the main project and it loads the toolchain file too.
try_compile could be used by the (user) project for check features of the library or of the compiler. There are many CMake modules which use try_compile in their implementation. E.g. CheckSymbolExists.
try_compile is also used by CMake itself, in platform files, when it perform basics checks for the compiler. In your log you could find the line:
CMake Error at /usr/share/cmake-3.16/Modules/CMakeTestCCompiler.cmake:44 (try_compile)
Aside from try_compile, the new CMake project is created in ExternalProject_Add command. That creation is also accompanied by the reading of the toolchain file. (More correctly, the new project is created not when ExternalProject_Add invocation is processed but when corresponding project is configured. This configuration is performed on the build stage of the main project.)

Value from CMakeCache.txt not used consistently during makefile generation [duplicate]

I'm having trouble setting a configuration variable via the command line. I can't determine it from the system, so I expect the user to specify:
cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain -DANDROID_ABI:STRING="arm64" ..
Inside my android.toolchain, I have the following:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
No matter what, it fails at this line EVEN THOUGH it prints out the correct arm64:
-- Android ABI: arm64
CMake Error at yaml-cpp/android.toolchain:45 (message):
Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64
Could anyone direct me to what I'm doing wrong?
I think this has to do with:
-D adds a cache variable instead of a normal variable
This is in a toolchain file... it seems to ignore cache variables
Any thoughts or suggestions?
I don't pretend to fully understand what's going on behind the scenes, but here's a workaround that works for me:
# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.
# Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.
if (FOO)
# Environment variables are always preserved.
set(ENV{_FOO} "${FOO}")
else ()
set(FOO "$ENV{_FOO}")
endif ()
CMake 3.6 introduces variable CMAKE_TRY_COMPILE_PLATFORM_VARIABLES which contains a list of variables, automatically passed from the main project to the project, created with try_compile.
A toolchain may add its variables to that list, so they could be extracted in a subproject:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
# propagate the variable into "inner" subprojects.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "ANDROID_ABI")
Caveats:
This approach affects only to source flow of try_compile command. It won't work when try_compile is used for create fully-fledged CMake project with signature
try_compile(<resultVar> <bindir> <srcdir> <projectName> ...)
(Approach with setting environment variable, as described in the #sorbet answer, works perfectly in this case.)
This approach won't work for a subproject, created with ExternalProject_Add.
(Approach with setting environment variable fails in that case too.)

CMake: how to change compiler for individual target

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!
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`).
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.
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_....
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.