CMake: what is the correct syntax for $<CONFIG:cfg_list> or $<IN_LIST:str,list>? - cmake

Using CMake version 3.20.1, I'm doing something that I consider to be relatively simple: verify that the value of cfg pass via -DCMAKE_BUILD_TYPE=<cfg> is a valid configuration.
Referencing the CMake generator expressions docs, it appears that $<CONFIG:cfgs> is a specialized case of $<IN_LIST:string,list> where the string is ${CMAKE_CONFIGURATION_TYPES}. The CMake runtime-error I'm getting reinforces this, as well as telling me that I'm missing something.
What I'm expecting to work is this:
set( CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Build Configurations" FORCE )
if( NOT $<CONFIG:${CMAKE_CONFIGURATION_TYPES}> )
# Handle_the_bad_value()
endif()
when called from the command line as cmake .. -DCMAKE_BUILD_TYPE=Debug, but I get the following error:
CMake Error at CMakeLists.txt:45 (if):
if given arguments:
"NOT" "\$<CONFIG:Debug" "Release>"
Unknown arguments specified
Replacing
$<CONFIG:<cfg_list>
with either:
$<IN_LIST:${CMAKE_BUILD_TYPE},${CMAKE_CONFIGURATION_TYPES}>
or
$<IN_LIST:"Debug","Debug;Release">
gives me the following:
CMake Error at CMakeLists.txt:43 (if):
if given arguments:
"NOT" "\$<IN_LIST:Debug,Debug" "Release>"
Unknown arguments specified
I've played around with various changes to the original syntax before experimenting with $<IN_LIST:str,list>, but being that I cannot get the IN_LIST syntax to work, I feel there is something fundamental that I am getting wrong that I'm also failing to grasp from the documentation.
Subsequent experimentation, using:
if( NOT ${CMAKE_BUILD_TYPE} IN_LIST ${CMAKE_CONFIGURATION_TYPES} )
yields:
CMake Error at CMakeLists.txt:43 (if):
if given arguments:
"NOT" "Debug" "IN_LIST" "Debug" "Release"
Unknown arguments specified
So I tried brute force:
if( NOT ( "Debug" IN_LIST "Debug;Release" ) )
which evaluates, but incorrectly, because it does not find "Debug" in the list, so it enters the code within the if-statement. Final try:
if( NOT ( CMAKE_BUILD_TYPE IN_LIST CMAKE_CONFIGURATION_TYPES ) )
Does work as expected. Noted: NOT should apply to an expression that is within parenthesis. But it still doesn't help me understand the correct syntax for the CONFIG generator expression.

You cannot use generator expressions in if command, because generator expressions are expanded only at the end of configuration process, but if condition needs to be evaluated immediately.
Expression $<IN_LIST:<$CONFIG>,${CMAKE_CONFIGURATION_TYPES}> is valid
in BOOL context of other generator expression. But it could be used only for produce another string, not for emit an error.
If you want to check correctness of the build type, specified by a user, following things should be taken into account:
On multi-configuration generators (like Visual Studio) a build type is given by --config option at build stage. CMake automatically checks such build type for match against CMAKE_CONFIGURATION_TYPES, so there is no needs in manual checks.
On single-configiration generators (like Makefile) a build type is specified via CMAKE_BUILD_TYPE variable. So there is no needs in a generator expression to refer a build type.
Variable CMAKE_CONFIGURATION_TYPES should be specified only for multi-configuration generators. Setting it for single-configuration generators could confuse some scripts (e.g. FindXXX.cmake).
So, you could check a build type in the following way (based on the code in that answer)
# Use custom variable for avoid setting CMAKE_CONFIGURATION_TYPES
# for single-configuration generators.
set(MY_CONFIGURATION_TYPES "Debug;Release")
# Check whether generator is single-configuration or multi-configuration
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
# Multi-configuration generator
# Set possible values. CMake automatically checks correctness.
set(CMAKE_CONFIGURATION_TYPES "${MY_CONFIGURATION_TYPES}" CACHE STRING "" FORCE)
else()
# Single-configuration generator
if(NOT CMAKE_BUILD_TYPE)
# If not set, set the build type to default.
message("Defaulting to release build.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
else()
# Check correctness of the build type entered by user
# Note that the right operand for IN_LIST should be **variable**,
# not just a list of values
if (NOT (CMAKE_BUILD_TYPE IN_LIST MY_CONFIGURATION_TYPES))
message(FATAL_ERROR "Incorrect build type")
endif()
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
# set the suggested values for cmake-gui drop-down list
# Note, that CMake allows a user to input other values.
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${MY_CONFIGURATION_TYPES}")
endif()

Using CMake consists of two stages:
configure stage cmake -S . -B _build or cd _build && cmake .. etc.
build stage cmake --build _build or cd _build && make etc.
Generator expressions $<suff> are expanded when building the project. They are not expanded inside CMake scripts. For CMake script, when configuring the project, it's just text.
To compare it in CMake, compare it in CMake. I guess:
if (CMAKE_BUILD_TYPE IN_LIST CMAKE_CONFIGURATION_TYPES)

Related

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()

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 check if preprocessor is defined

I can't get cmake to test if a preprocessor has been defined or not. Eg:
cmake_minimum_required(VERSION 2.8.9)
project (cmake-test)
add_definitions(-DOS=LINUX)
if(NOT <what condition goes here?>)
message(FATAL_ERROR "OS is not defined")
endif()
The following tests don't work:
if (NOT COMMAND OS)
if (NOT DEFINED OS)
if (NOT OS)
I can get it to work by using set() and just testing for a regular cmake variable and then defining the preprocessor macro. Eg:
set(OS LINUX)
if (OS)
add_definitions(-DOS=${OS})
else()
message(FATAL_ERROR "OS is not defined")
endif()
In case, you're wondering why I need to test it if the variable/preprocessor is in the same file, it's because in the final implementation these will come from an external file which is includeed in the main CMakeFile.txt Eg:
include(project_defs.txt)
if (OS)
....
This is to complete the answer by arrowd.
I also tried the COMPILE_DEFINITIONS option as mentioned above by arrowd unsuccessfully.
Following the documentation of CMake, at least for version 3.x, it turns out that when you call add_definitions() in CMake, it adds the definitions to the COMPILE_DEFINITIONS directory property.
Therefore, lets say you are defining the following as per your code:
add_definitions(-DOS=LINUX)
To retrieve the string with the definitions added into the variable "MYDEFS" you can use the following lines in CMake:
get_directory_property(MYDEFS COMPILE_DEFINITIONS)
MESSAGE( STATUS "Compile defs contain: " ${MYDEFS} )
Then you can check if in ${MYDEFS} the define you are looking for exists or not. For instance
if(MYDEFS MATCHES "^OS=" OR MYDEFS MATCHES ";OS=")
MESSAGE( STATUS "OS defined" )
else()
# You can define your OS here if desired
endif()
Normally, all definitions that are passed to the compiler are controlled by CMake. That is, you create a CMake variable with
option(SOMEFEATURE "Feature description" ON)
or
set(OS "" CACHE STRING "Select your OS")
User sets them via cmake -D OS=DOS or in the CMake GUI. Then you can use if() operator to conditionally add_definitions() to the compiler command line.
UPDATE:
If you really want to access preprocessor flags, there is a COMPILE_DEFINITIONS target property. You can access it this way:
get_target_property(OUTVAR target COMPILE_DEFINITIONS)

How to check if generator is a multi-config generator in a CMakeLists.txt

The Cmake FAQ
and
other
places
recommend to check CMAKE_CONFIGURATION_TYPES to recognize a multi-configuration generator. I have found several questions where this did not work (for example this one). The issue seems to be that the variable is not set the first time cmake is called.
I tested with the following file
cmake_minimum_required(VERSION 2.6)
if(CMAKE_CONFIGURATION_TYPES)
message("Multi-configuration generator")
else()
message("Single-configuration generator")
endif()
project(foo)
and called it like this
mkdir build
cd build
cmake -G "Visual Studio 12 2013" ..
and got Single-configuration generator.
How should I distinguish whether the current generator supports multiple configurations?
EDITED: Added information on checking and changing CMAKE_CONFIGURATION_TYPES
Check and Changing CMAKE_CONFIGURATION_TYPES
Taking the suggestions from this question you could check and change CMAKE_CONFIGURATION_TYPES, but be aware that there was a bug 0015577: The 'project' command overwrites CMAKE_CONFIGURATION_TYPES in CMake 3.2.2 that did break this behaviour for the initial VS solution generation (fixed with CMake 3.3.0):
cmake_minimum_required(VERSION 3.3)
project(foo NONE)
if(CMAKE_CONFIGURATION_TYPES)
message("Multi-configuration generator")
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "My multi config types" FORCE)
else()
message("Single-configuration generator")
endif()
enable_language(C CXX)
Preset CMAKE_CONFIGURATION_TYPES
If you just need a certain set of configurations for multi-configuration environments you can do (thanks to #Tsyvarev for the suggestion):
cmake_minimum_required(VERSION 2.8)
# NOTE: Only used in multi-configuration environments
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "My multi config types" FORCE)
project(foo)
None multi-configuration environments will just ignore it. But be aware that other CMake modules like findBoost.cmake, findCUDA.cmake may rely on CMAKE_CONFIGURATION_TYPES being empty for single-configuration environments (thanks again #Tsyvarev for the hint).
So a better solution would be adding toolchain files for all your supported generators. They are generally useful, because there you can handle all the toolchain/generator specific parts.
Here is an extract of my VSToolchain.txt:
# Reduce the config types to only Debug and Release
SET(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
# Standard is a console app. If you need a windows app, use WIN32 define in add_executable
set(CMAKE_WIN32_EXECUTABLE 0 CACHE INTERNAL "")
CMAKE_WIN32_EXECUTABLE is just there to show what kind of settings I have put in my Visual Studio toolchain file.
Another CMake command line solution is suggested here: How to create cmake build configuration without debug symbols and without optimizations?
Only Checking CMAKE_CONFIGURATION_TYPES
If you only want do check what CMake does set in CMAKE_CONFIGURATION_TYPES:
I just tested your above code with Visual Studio 2013 and MinGW/GCC (both with empty build directories). You just need one small change and move the check after the project() command:
project(foo)
message("CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES}")
if(CMAKE_CONFIGURATION_TYPES)
message("Multi-configuration generator")
else()
message("Single-configuration generator")
endif()
And I get for VS2013:
CMAKE_CONFIGURATION_TYPES Debug;Release;MinSizeRel;RelWithDebInfo
Multi-configuration generator
And for GCC:
CMAKE_CONFIGURATION_TYPES
Single-configuration generator
For more details about what CMake does see:
CMAKE_CONFIGURATION_TYPES set by EnableLanguage() in cmGlobalVisualStudio7Generator.cxx
CMake: In which Order are Files parsed (Cache, Toolchain, …)?
I see you are on CMake v2.6, but for anyone who is on v3.9+, v3.9 introduced the global property called GENERATOR_IS_MULTI_CONFIG:
Read-only property that is true on multi-configuration generators.
You can load the value into a CMake variable like so:
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
This very approach is recommended in "Professional CMake" by Craig Scott, along with explanations of the shortcomings of other approaches- especially those involving CMAKE_CONFIGURATION_TYPES. The book is $30 but the section I'm referring to is in the sample chapters.

Check CMake Cache Variable in Toolchain File

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.)