CMake: How to access a variable from a subdirectory without explicitly setting it in parent scope - cmake

I have added a subdirectory in CMake by using add_subdirectory. How can I access a variable from the scope of that subdirectory without explicitly setting the variable by using set in combination with PARENT_SCOPE ?
set(BOX2D_BUILD_STATIC 1)
set(BOX2D_BUILD_EXAMPLES 0)
set(BOX2D_INSTALL_BY_DEFAULT 0)
add_subdirectory(Box2D_v2.2.1)
message(STATUS "Using Box2D version ${BOX2D_VERSION}")
# how to get ${BOX2D_VERSION} variable without modifying CMakeLists.txt in Box2D_v2.2.1?
Is this possible?

If the variable is a plain variable (as opposed to a cache variable), there is no way to access it from the parent scope.
Cache variables (those set with set(... CACHE ...)) can be accessed regardless of scope, as can global properties (set_property(GLOBAL ...)).

While #Angew's answer is correct, there aren't many things that are really impossible with CMake :-)
If you have a line like
set(BOX2D_VERSION 2.2.1)
in Box2D_v2.2.1/CMakeLists.txt, then you can retrieve the version in the parent scope by doing something like:
file(STRINGS Box2D_v2.2.1/CMakeLists.txt VersionSetter
REGEX "^[ ]*set\\(BOX2D_VERSION")
string(REGEX REPLACE "(^[ ]*set\\(BOX2D_VERSION[ ]*)([^\\)]*)\\)" "\\2"
BOX2D_VERSION ${VersionSetter})
This is a bit fragile; it doesn't accommodate for extra spaces in the set command for example, or cater for the value being set twice. You could cater for these possibilities too, but if you know the format of the set command and it's unlikely to change, then this is a reasonable workaround.

Related

What is the usage idiom of cmake cache for improving performance

Is this the right idiom to discover a variable value and cache it only if it hasn't been cached already? Using the path type is just for example purposes.
IF(NOT $CACHE{PATH_CACHED})
#discover the PATH_CACHED value and store it in PATH_NORMAL
SET(PATH_CACHED ${PATH_NORMAL} CACHE FILEPATH "My path")
ENDIF(NOT $CACHE{PATH_CACHED})
Use if (NOT DEFINED), like so:
if (NOT DEFINED PATH_CACHED)
# do something expensive to compute PATH_CACHED as a normal
# variable
endif ()
set(PATH_CACHED "${PATH_CACHED}"
CACHE FILEPATH "My path")
Two main points here:
Using if (NOT DEFINED X) is a more precise check than if (NOT X) since someone might want to set X to empty.
Unconditionally setting PATH_CACHED to the computed normal variable has more consistent behavior across a range of CMake versions and also preserves the behavior of adjusting a path relative to the process working directory when an untyped cache variable (set on the command line as -DPATH_CACHED=foo) gets a type of PATH or FILEPATH. See the docs here: https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-D
Also there hasn't been any need to use all-caps command names or to repeat the condition in endif() for almost twenty years.

Make CMake variable set in subproject (variable nesting) available to all other subprojects

If I have a similar to the following structure project tree (each subproject is added via add_subdirectory() to the parent project):
CMakeLists.txt (TOP-LEVEL)
|
+ -- subproj1
| |
| + -- CMakeLists.txt (subproj1)
| |
| + -- subsubproj1
| |
| + -- CMakeLists.txt (subsubproj1)
|
+ -- subproj2
|
+ -- CMakeLists.txt (subproj2)
I want to expose a variable set inside subsubproj1 in subproj2. The nature of this variable is irrelevant but in my case it points at ${CMAKE_CURRENT_SOURCE_DIR}/include that is the include directory of subsubproj1, which (in my case) is a library used by subproj2. Currently I am re-assigning the variable in each intermediate CMakeLists.txt between the one (here subproj1) where the variable was assigned a value to the top-level with PARENT_SCOPE enabled:
CMakeLists.txt (subsubproj1)
# Expose MY_VAR to subproj1
set(MY_VAR
"Hello"
PARENT_SCOPE
)
CMakeLists.txt (subproj1)
# Expose MY_VAR to top level project thus making it visible to all
set(MY_VAR
${MY_VAR}
PARENT_SCOPE
)
This can be applied to an arbitrary nested project tree.
My question is what is the common practice of doing what I have described above? I can declare MY_VAR as a top-level variable to begin with but what if for some reason I don't want to make it visible (as in written text) there. In which case is PARENT_SCOPE no longer an option and should be replaced with just a straight declaration of that variable in the top-level CMakeLists.txt?
Targets
The nature of this variable is irrelevant but in my case it points at ${CMAKE_CURRENT_SOURCE_DIR}/include that is the include directory of subsubproj1, which (in my case) is a library used by subproj2.
No, the nature is not irrelevant.
Using variables to communicate include directories in CMake is a horrible anti-pattern from the 2.6.x days. You should not use a hammer to drive in a screw.
Non-IMPORTED projects are always global, so you can link to them safely. In subsubproj1 you would write:
add_library(myproj_subsubproj1 INTERFACE)
add_library(myproj::subsubproj1 ALIAS myproj_subsubproj1)
target_include_directories(
myproj_subsubproj1
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
Then in subproj2, you would write:
target_link_libraries(myproj_subproj2 PRIVATE myproj::subsubproj1)
Worse options
The following options are worse because they forego the declarative parts of the CMake language and make your scripts dependent on subproject inclusion order. This is a significant complexity increase that (in my experience) is not warranted in build code.
Nevertheless, here are the imperative tools CMake provides:
1. Using a CACHE variable
The cache is a disk-persisted store for global variables. Setting one in any directory makes the value visible to all directories.
Note that there are a few potential drawbacks to this:
Prior to CMake 3.21, creating a new cache entry would delete a normal variable of the same name, leading to tricky situations where builds could become non-idempotent (bad!). See https://cmake.org/cmake/help/latest/policy/CMP0126.html
The user can overwrite your cache variables at the command line, so you cannot rely on either the defined-ness or the value of the variable when your CMake program starts running.
If you can live with this, then you can write:
# On CMake <3.21, honor normal variables. Can remove
# check if on CMake >=3.21
if (NOT DEFINED MyProj_INADVISABLE_VARIABLE)
set(MyProj_INADVISABLE_VARIABLE
"${CMAKE_CURRENT_SOURCE_DIR}/include>"
CACHE STRING "Doc string...")
# If you want to hint to your users that they should
# not edit this variable, include the following line:
mark_as_advanced(MyProj_WEIRD_VARIABLE)
endif ()
If you do not want to allow your users to override it, then you may consistently use an INTERNAL cache variable:
set(MyProj_INADVISABLE_VARIABLE "..." CACHE INTERNAL "...")
As long as you initialize it to a known value early on, then this will work okay as a global variable, but might incur disk traffic on writes.
2. Directory property
A slightly better approach is to use a custom directory property to communicate a value. In subsubproj1:
set_property(DIRECTORY "." PROPERTY inadvisable_prop "foo")
Then in subproj2:
get_property(value DIRECTORY "../subproj1/subsubproj1"
PROPERTY inadvisable_prop)
Note that it is not an error to get a non-existent property, so be on the lookout for types.
You could also use a GLOBAL property instead of a directory property, but global variables in general are a headache waiting to happen. You might as well set it on the directory to decrease the chances of unintended scoping bugs.

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.

CMake SET command not working as expected (CMake not setting value of predefined cached variable)

I'm using CMake in my builds. I'm trying to set compiler flags like this:
set(CMAKE_C_FLAGS_DEBUG "-option1 -option2 -option3")
however, my build fails because one of the compiler flags is not set.
When I check the value of the variable in CMake-GUI is empty:
Can someone point out what is happening?
What you see with cmake-gui is the cache status. You only see there variables that you explicitly cache, or predefined cmake cached variables.
Now, when you do:
set(CMAKE_C_FLAGS_DEBUG "-option1 -option2 -option3")
You are doing something particular: you are setting a "local" (not cached) variable which has the same name of a predefined cmake cached variable. In C++ it would be like defining a local variable with the same name of a global variable: the local variable will hide your global variable.
From the set documentation. (The documentation calls "normal" what I called "local")
Both types can exist at the same time with the same name but different
values. When ${FOO} is evaluated, CMake first looks for a normal
variable 'FOO' in scope and uses it if set. If and only if no normal
variable exists then it falls back to the cache variable 'FOO'.
You are already effectively setting CMAKE_C_FLAGS_DEBUG, and the compiler will use the flags you have specified. You can check it with:
message(STATUS "CMAKE_C_FLAGS_DEBUG = ${CMAKE_C_FLAGS_DEBUG}")
So, your building is failing for another reason. Check which command line make generates: make VERBOSE=1.
By the way, I suggest you append your flags to the predefined ones, by doing:
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -option1 -option2 -option3")
Also consider using the other predefined variable CMAKE_C_FLAGS, in case you want those settings to be propagated to all build types.
Edit:
After some iterations it turned out the problem was that the build type (CMAKE_BUILD_TYPE) was not set from cmake. This can either be done through the cmake-gui interface, or adding something like -DCMAKE_BUILD_TYPE=Debug or -DCMAKE_BUILD_TYPE=Release to the cmake command line.

Use environment variables as default for cmake options

I would like to set up a cmake script with some options (flags and strings). For some of the options I would like to use environment variables as a default. Basically, I'm trying to emulate something like MY_OPTION ?= default in a Makefile. I tried the following:
project (Optiontest)
option(MY_OPTION
"Documentation"
$ENV{MY_OPTION}
FORCE)
message("value: ${MY_OPTION}")
I called this in the following way:
$ cmake -DMY_OPTION=ON .
value: ON
$ cmake -DMY_OPTION=OFF .
value: OFF
$ MY_OPTION=OFF cmake .
value: OFF
$ MY_OPTION=ON cmake .
value: OFF
My problem is that the last line should be ON as well.
For bonus karma: I would actually prefer three levels of preference. The value of -DMY_OPTION should be used if given. If not, the value of a set environment variable MY_OPTION should be used. If this is also not set, a constant should be used. I guess, I could use a bunch of nested if statements and somehow check if the variables are set, but I don't know how and I hope there is a better way.
FORCE is (as of CMake 3.0.2) not a valid parameter for option.
This is the primary source of problems. CMake will interpret the string FORCE as the desired initial value of the option in absence of an environment variable. The usual contrived rules for string-to-truth-value-conversion apply, resulting in the option being set to OFF by this call.
Second, you need to account for the fact that the environment variable is not set. Your current code misses to handle that case properly. $ENV{MY_OPTION} will evaluate to the empty string in that case. If you evaluate the set values in both the cache and the environment, you can enforce any behavior that you want.
In general, you should think about what you actually want here. Usually, FORCE setting a cached variable is a bad idea and I would not be surprised if you found your initial argument for doing this flawed after some careful reevaluation.
Maybe value of MY_OPTION cached in CMake cache? Do you try to clean cmake cache after third call MY_OPTION=OFF cmake .?