How should I do when different user-set CMake configuration options clash? - cmake

Suppose I have two cached CMake strings for the user to set, but some of the value combinations clash.
Example:
set(FOO OFF CACHE BOOL "Foo")
set(BAR OFF CACHE BOOL "Bar")
but you can't have both Foo and Bar.
Obviously, we can prevent this with:
if (FOO AND BAR)
message(FATAL_ERROR "Can't have both Foo and Bar.")
endif()
but is there some other mechanism? e.g. something which, in ccmake, would make the value of BAR revert to false if FOO is set to true and vice-versa?
Note: This is a simplified example. In the simple case one could just opt for a single tri-state option, but that wouldn't work when you have multiple possible values and a somewhat more complex forbidden-combination predicate.

cmake_dependent_option can be used for this purpose, it sets an option based on another set of variables being true.
For example,
cmake_option(FOO "Use Foo" ON)
cmake_dependent_option(BAR "Use Bar" ON "NOT FOO" OFF)
For further information see CMake documentation

You may want to use set's FORCE option to force-set a CMake cache variable value.
if (FOO)
set(BAR OFF CACHE BOOL "Bar" FORCE)
elseif (BAR)
set(FOO OFF CACHE BOOL "Foo" FORCE)
endif()
This will be reflected in the GUI after the next Configure pass.

Related

CMake how to check that exactly one option is selected?

Assuming I have 3 defined options in a CMakeLists file, for instance:
option(FIRST_OPT "<text1>" ON)
option(SECOND_OPT "text2>" OFF)
option(THIRD_OPT "<text3>" OFF)
Exactly one option (of the three) should be selected. Is there an elegant way/ Macro to verify it?
Currently I just check for all valid conditions
(which, for 3 options still looks reasonable but for larger groups is not practical):
(FIRST_OPT AND SECOND_OPT) OR
(FIRST_OPT AND THIRD_OPT) OR
(SECOND_OPT AND THIRD_OPT) OR
(NOT (FIRST_OPT AND SECOND_OPT AND THIRD_OPT))
Instead of mutually-exclusive set of options use a single parameter with a string value:
set(OPT "FIRST" CACHE STRING "Provider choosen")
This parameter is visible to the user in the same extent, as one created by option command.
Because the parameter can have only a single value, you needn't to check whether at most one of your options is enabled.
By assigning STRINGS property of the parameter, you may tell a user which parameter's values can be used, and even provide drop-down list of them in the GUI:
set_property(CACHE OPT PROPERTY STRINGS "FIRST" "SECOND" "THIRD")
CMake itself doesn't check correctness of the parameter's value, but you may easily check it by yourself:
# Extract "STRINGS" property of the parameter
get_property(OPT_STRINGS CACHE OPT PROPERTY STRINGS)
# Check that value of the parameter is inside "STRINGS" list.
if (NOT OPT IN_LIST OPT_STRINGS)
message(FATAL_ERROR "Wrong value of the parameter 'OPT'")
endif
Based on nayab comment:
option(FIRST_OPT "<text1>" ON)
option(SECOND_OPT "<text2>" OFF)
option(THIRD_OPT "<text3>" OFF)
set(COUNTER 0)
set(ALL_OPTIONS FIRST_OPT;SECOND_OPT;THIRD_OPT)
foreach(option IN LISTS ALL_OPTIONS)
if(${option})
math(EXPR COUNTER "${COUNTER+1}")
endif()
endforeach()
if(NOT ${COUNTER})
message("No option was chosen")
elseif(${COUNTER} GREATER 1)
message("More than one option was chosen")
endif()

CMake set variable

In the following CMake code snippet, I am confused by the if elseif check. My understanding is that BL will always be "Ei", so there is no need to check other values. Are there any scenarios where BL could be overwritten by something else? I am new to CMake so need some help here.
set(BL "Ei" CACHE STRING "library")
set_property(CACHE BL PROPERTY STRINGS "Ei;AT;Op")
message(STATUS "The backend of choice:" ${BL})
if(BL STREQUAL "Ei")
...
elseif(BL STREQUAL "AT")
...
elseif(BL STREQUAL "Op")
...
else()
message(FATAL_ERROR "Unrecognized option:" ${BL})
endif()
The code set(BL "Ei" CACHE STRING "library") defines a CMake cache variable. However, without a FORCE option in the set statement, that means that it will not be overwritten if the variable was previously defined in the cache.
One way for a user to set a different value for BL would be on the cmake command line. For example: cmake ../sourcedir -DBL:STRING=AT
By entering the variable in the cache as type STRING (as opposed to type INTERNAL) that also makes the variable available to be configured in cmake-gui or in ccmake. (Furthermore, the set_property(... STRINGS ...) directive tells cmake-gui to produce a drop-down list containing Ei, AT, and Op to select from. However, this isn't enforced for setting the variable from the command line, which is why it's still a good idea to have the default case signalling an error.)
See the section "Set Cache Entry" under CMake's documentation for set for more information.

How to specify the variable which must be set and exist in cmake

I have a need to have some variable to be specified and exist in the environment.
In case it does not exist need to stop building.
example
if ( "${VARMUSTEXIST}" STREQUAL "ON" )
message(STATUS is ON)
elif ("${VARMUSTEXIST}" STREQUAL "OFF")
message(STATUS is OFF)
endif()
I don't want to put an if (defined VARMUSTEXIST) everywhere in the script.
In bash there is an option for that "set -u".
Some preliminary points:
if ( "${VARMUSTEXIST}" STREQUAL "ON" ) [...] elif(AGAIN LONG EXPRESSION) [...] endif()normally in cmake is simply: if (VARMUSTEXIST) [...] else() [...] endif()
The command if (DEFINED VARMUSTEXIST) requires DEFINED to be upper case.
You mention bash and environment variables:Environment variables are read using $ENV{VARIABLE_NAME}
For environment variables you will do:
if(NOT DEFINED ENV{VARMUSTEXIST})
message(FATAL_ERROR "You must set VARMUSTEXIST environment variable")
endif()
You say:
I don't want to put an if (defined VARMUSTEXIST) everywhere in the script
This is not clear to me: for each variable you need to check only once, possibly in the main CMakeLists.txt. Of course, you need to add NOT: if (NOT DEFINED VARMUSTEXIST) [stop]
If you can be more precise on your problem, we can design a macro that checks if one or a group of variables are defined or not.
If by environment you mean OS environment variables then the syntax is wrong anyway.
If those are options to be provided by the user, then the literal comparisons with ON and OFF are incorrect, since CMake has more ways to express booleans and they are all in widespread use and became idiomatic. Thus, by expecting either ON or OFF, you're making your build script weird and against everyone's expectations. And you're also making more work for yourself, as you've noticed.
First, you should document the options and give them safe default values using option(). Then it won't ever be that a variable could be undefined.
# early in the top-level CMakeLists.txt
option(VARMUSTEXIST "You can provide it. It exists anyway." NO)
And then you'll check its truth or falsehood rather simply:
# later in top-level file or in subdirectories
if (VARMUSTEXIST)
message("VARMUSTEXIST is true")
else()
message("VARMUSTEXIST is false")
endif()
I think this is the best approach...
CMake Variable:
if(NOT DEFINED VARIABLE)
message(FATAL_ERROR "VARIABLE is not set")
endif(NOT DEFINED VARIABLE)
Environment Variable:
if(NOT DEFINED ENV{VARIABLE})
message(FATAL_ERROR "VARIABLE is not set")
endif(NOT DEFINED ENV{VARIABLE})

What's the CMake syntax to set and use variables?

I'm asking this as a reminder to myself the next time I use CMake. It never sticks, and Google results aren't great.
What's the syntax to set and use variables in CMake?
When writing CMake scripts there is a lot you need to know about the syntax and how to use variables in CMake.
The Syntax
Strings using set():
set(MyString "Some Text")
set(MyStringWithVar "Some other Text: ${MyString}")
set(MyStringWithQuot "Some quote: \"${MyStringWithVar}\"")
Or with string():
string(APPEND MyStringWithContent " ${MyString}")
Lists using set():
set(MyList "a" "b" "c")
set(MyList ${MyList} "d")
Or better with list():
list(APPEND MyList "a" "b" "c")
list(APPEND MyList "d")
Lists of File Names:
set(MySourcesList "File.name" "File with Space.name")
list(APPEND MySourcesList "File.name" "File with Space.name")
add_excutable(MyExeTarget ${MySourcesList})
The Documentation
CMake/Language Syntax
CMake: Variables Lists Strings
CMake: Useful Variables
CMake set() Command
CMake string()Command
CMake list() Command
Cmake: Generator Expressions
The Scope or "What value does my variable have?"
First there are the "Normal Variables" and things you need to know about their scope:
Normal variables are visible to the CMakeLists.txt they are set in and everything called from there (add_subdirectory(), include(), macro() and function()).
The add_subdirectory() and function() commands are special, because they open-up their own scope.
Meaning variables set(...) there are only visible there and they make a copy of all normal variables of the scope level they are called from (called parent scope).
So if you are in a sub-directory or a function you can modify an already existing variable in the parent scope with set(... PARENT_SCOPE)
You can make use of this e.g. in functions by passing the variable name as a function parameter. An example would be function(xyz _resultVar) is setting set(${_resultVar} 1 PARENT_SCOPE)
On the other hand everything you set in include() or macro() scripts will modify variables directly in the scope of where they are called from.
Second there is the "Global Variables Cache". Things you need to know about the Cache:
If no normal variable with the given name is defined in the current scope, CMake will look for a matching Cache entry.
Cache values are stored in the CMakeCache.txt file in your binary output directory.
The values in the Cache can be modified in CMake's GUI application before they are generated. Therefore they - in comparison to normal variables - have a type and a docstring. I normally don't use the GUI so I use set(... CACHE INTERNAL "") to set my global and persistant values.
Please note that the INTERNAL cache variable type does imply FORCE
In a CMake script you can only change existing Cache entries if you use the set(... CACHE ... FORCE) syntax. This behavior is made use of e.g. by CMake itself, because it normally does not force Cache entries itself and therefore you can pre-define it with another value.
You can use the command line to set entries in the Cache with the syntax cmake -D var:type=value, just cmake -D var=value or with cmake -C CMakeInitialCache.cmake.
You can unset entries in the Cache with unset(... CACHE).
The Cache is global and you can set them virtually anywhere in your CMake scripts. But I would recommend you think twice about where to use Cache variables (they are global and they are persistant). I normally prefer the set_property(GLOBAL PROPERTY ...) and set_property(GLOBAL APPEND PROPERTY ...) syntax to define my own non-persistant global variables.
Variable Pitfalls and "How to debug variable changes?"
To avoid pitfalls you should know the following about variables:
Local variables do hide cached variables if both have the same name
The find_... commands - if successful - do write their results as cached variables "so that no call will search again"
Lists in CMake are just strings with semicolons delimiters and therefore the quotation-marks are important
set(MyVar a b c) is "a;b;c" and set(MyVar "a b c") is "a b c"
The recommendation is that you always use quotation marks with the one exception when you want to give a list as list
Generally prefer the list() command for handling lists
The whole scope issue described above. Especially it's recommended to use functions() instead of macros() because you don't want your local variables to show up in the parent scope.
A lot of variables used by CMake are set with the project() and enable_language() calls. So it could get important to set some variables before those commands are used.
Environment variables may differ from where CMake generated the make environment and when the the make files are put to use.
A change in an environment variable does not re-trigger the generation process.
Especially a generated IDE environment may differ from your command line, so it's recommended to transfer your environment variables into something that is cached.
Sometimes only debugging variables helps. The following may help you:
Simply use old printf debugging style by using the message() command. There also some ready to use modules shipped with CMake itself: CMakePrintHelpers.cmake, CMakePrintSystemInformation.cmake
Look into CMakeCache.txt file in your binary output directory. This file is even generated if the actual generation of your make environment fails.
Use variable_watch() to see where your variables are read/written/removed.
Look into the directory properties CACHE_VARIABLES and VARIABLES
Call cmake --trace ... to see the CMake's complete parsing process. That's sort of the last reserve, because it generates a lot of output.
Special Syntax
Environment Variables
You can can read $ENV{...} and write set(ENV{...} ...) environment variables
Generator Expressions
Generator expressions $<...> are only evaluated when CMake's generator writes the make environment (it comparison to normal variables that are replaced "in-place" by the parser)
Very handy e.g. in compiler/linker command lines and in multi-configuration environments
References
With ${${...}} you can give variable names in a variable and reference its content.
Often used when giving a variable name as function/macro parameter.
Constant Values (see if() command)
With if(MyVariable) you can directly check a variable for true/false (no need here for the enclosing ${...})
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number.
False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in the suffix -NOTFOUND.
This syntax is often use for something like if(MSVC), but it can be confusing for someone who does not know this syntax shortcut.
Recursive substitutions
You can construct variable names using variables. After CMake has substituted the variables, it will check again if the result is a variable itself. This is very powerful feature used in CMake itself e.g. as sort of a template set(CMAKE_${lang}_COMPILER ...)
But be aware this can give you a headache in if() commands. Here is an example where CMAKE_CXX_COMPILER_ID is "MSVC" and MSVC is "1":
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") is true, because it evaluates to if("1" STREQUAL "1")
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") is false, because it evaluates to if("MSVC" STREQUAL "1")
So the best solution here would be - see above - to directly check for if(MSVC)
The good news is that this was fixed in CMake 3.1 with the introduction of policy CMP0054. I would recommend to always set cmake_policy(SET CMP0054 NEW) to "only interpret if() arguments as variables or keywords when unquoted."
The option() command
Mainly just cached strings that only can be ON or OFF and they allow some special handling like e.g. dependencies
But be aware, don't mistake the option with the set command. The value given to option is really only the "initial value" (transferred once to the cache during the first configuration step) and is afterwards meant to be changed by the user through CMake's GUI.
References
How is CMake used?
cmake, lost in the concept of global variables (and PARENT_SCOPE or add_subdirectory alternatives)
Looping over a string list
How to store CMake build settings
CMake compare to empty string with STREQUAL failed
When should I quote CMake variables?
Here are a couple basic examples to get started quick and dirty.
One item variable
Set variable:
SET(INSTALL_ETC_DIR "etc")
Use variable:
SET(INSTALL_ETC_CROND_DIR "${INSTALL_ETC_DIR}/cron.d")
Multi-item variable (ie. list)
Set variable:
SET(PROGRAM_SRCS
program.c
program_utils.c
a_lib.c
b_lib.c
config.c
)
Use variable:
add_executable(program "${PROGRAM_SRCS}")
CMake docs on variables
$ENV{FOO} for usage, where FOO is being picked up from the environment variable. otherwise use as ${FOO}, where FOO is some other variable. For setting, SET(FOO "foo") would be used in CMake.

Upper level seeing a lower level option

I have a lower level project that builds several libraries. The upper level takes the libs and links them into many executables. The lower level has this option:
option(TOOLA_SUPPORT "Ena/Disable toolA support" OFF)
I'd like to get the upper level build to see the TOOLA_SUPPORT flag from the lower level build. As it is now, there are 2 TOOLA_SUPPORT flags. One at the lower, the other at the upper level builds. This is a hassle as they both need to be in the same state for a successful build.
I'm not sure how to get one CMakeLists.txt file to inherit a flag from another (cache). (Only want this one flag, not the whole lower CMakeLists.txt/cache settings.) What commands might help, or where to look for info?
Edit_1:
dirUpper/src/CMakeLists.txt (Does NOT contain dirLower) I'd like this cmakelists to "inherit" the cache var TOOLA_SUPPORT from dirLower/src/CMakeLists.txt
option(TOOLA_SUPPORT "" OFF)
add_subdirectory(${dirLower}/src ${dirLower}/lib EXCLUDE_FROM_ALL)
if(TOOLA_SUPPORT)
link_directories(blah bla bla)
else()
link_directories(blah bla)
endif()
dirLower/src/CMakeLists.txt (Is not part of the dirUpper tree) I'd like to pass TOOLA_SUPPORT from this cmakelists, to dirUpper/src/cmakelists.txt
IF(NOT (${COMPILER_FLAGS} MATCHES "-Wall")) #this only applies to dirLower
MESSAGE(FATAL_ERROR "compiler flag not properly set") #will crash the dirUpper 'config'
ENDIF()
OPTION(TOOLA_SUPPORT "" OFF)
IF(TOOLA_SUPPORT)
INCLUDE_DIRECTORIES(some/dir/include)
SET(TOOLA_SUPPORT ON CACHE "" FORCE PARENT_SCOPE)
ELSE()
MESSAGE("TOOLA is not supported")
SET(TOOLA_SUPPORT OFF CACHE "" FORCE PARENT_SCOPE)
ENDIF()
When I run the dirLower file, I get a cmake warning telling me there is no parent for it. (Not part of the dirUpper tree)
When I run the dirUpper file, I get a fatal error due to the COMPILER_FLAG test in the dirLower file. When I bypass this issue, Im not seeing the TOOLA_SUPPORT setting being inherited from the dirLower.
Assuming the lower level is "included" in the upper level with ADD_SUBDIRECTORY(), cache variables should already be shared between upper and lower. You can use a command-line option to set the value, or use SET( ... CACHE ... FORCE) to be sure the value gets set as you actually intend. Then just reference it from the other level.
If instead you want regular variables, it appears that regular variables set before the ADD_DIRECTORY() are passed down to the lower level. And to pass a regular variable UP a level, use SET(... PARENT_SCOPE).
EDIT:
Writing out in code my top suggest:
In file ~/test/CMakeLists.txt:
OPTION(TOOLA_SUPPORT "" OFF)
ADD_SUBDIRECTORY(test2)
MESSAGE(STATUS "${TOOLA_SUPPORT}")
In file ~/test/test2/CMakeLists.txt:
SET(TOOLA_SUPPORT ON CACHE BOOL "" FORCE)
The output is "ON".
I think the trouble you're running into is that once a cache value is set, it doesn't get changed, so the top-level value holds. That's where the FORCE comes in.
If you're still having difficulty, please post a minimum code sample that demonstrates what's not working.