Prevent cmake from allowing any value for CMAKE_CONFIGURATION_TYPES - cmake

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

Related

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

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)

CMake if(DEFINED MY_COMPILE_DEF) does not work. How can I use precompile definition as conditional?

I'm trying to use a compile definition as a conditional to build a Gtest executable. The problematic CMake code is as follows:
add_compile_definitions(TEST_BENCH)
if(DEFINED TEST_BENCH)
enable_testing()
endif()
This does not work though. I've read a few similar questions and answers that have been regarding the use of ${my_var} syntax which is not the case in my code.
Can compile definitions be used in conditionals, and if so how?
Use a cmake (cache) variable, which will also allow users to configure your project properly.
set(TEST_BENCH OFF CACHE BOOL "Enables testing of your project")
if(TEST_BENCH)
add_compile_definition(TEST_BENCH)
enable_testing()
endif()
Then the user (and you) can configure your project according to their needs with the help of ccmake or cmake-gui or with cmake -DTEST_BENCH=ON. I believe target_compile_definitions is generally preferred over global add_compile_definitions.
Still, I wouldn't advise it, you can match COMPILE_DEFINITIONS variable that is modified by add_compile_definition with the TEST_BENCH and that way check if the macro is set or not.
In short, add_compile_definitions() is for source files while set() is for CMake variables. if(DEFINED) is to check your CMake or env variables thus you need to use set().
Credit to #squareskittles
https://stackoverflow.com/a/61815468/2324483

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)

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