Variables explained

Overview

Teaching: 10 min
Exercises: 10 min
Questions
  • How do variables work?

Objectives
  • Learn about local variables.

  • Understand that cached variables persist across runs.

  • Know how to glob, and why you might not do it.

Variables

For this exercise, we will just directly run a CMake Script, instead of running CMakeLists.txt. The command to do so is:

# Assuming you have a file called example.cmake:
cmake -P example.cmake

This way, we don’t have so many little builds sitting around.

Local variables

Let’s start with a local variable.

# local.cmake
set(MY_VARIABLE "I am a variable")
message(STATUS "${MY_VARIABLE}")

Here we see the set command, which sets a variable, and the message command, which prints out a string. We are printing a STATUS message - there are other types (many other types in CMake 3.15+).

More about variables

Try the following:

  • Remove the quotes in set. What happens?
  • Remove the quotes in message. What happens? Why?
  • Try setting a cached variable using -DMY_VARIABLE=something before the -P. Which variable is shown?

Cached variables

Now, let’s look at cached variables; a key ingredient in all CMake builds. In a build, cached variables are set in the command line or in a graphical tool (such as ccmake, cmake-gui), and then written to a file called CMakeCache.txt. When you rerun, the cache is read in before starting, so that CMake “remembers” what you ran it with. For our example, we will use CMake in script mode, and that will not write out a cache, which makes it easier to play with. Feel free to look back at the example you built in the last lesson and investigate the CMakeCache.txt file in your build directory there. Things like the compiler location, as discovered or set on the first run, are cached.

Here’s what a cached variable looks like:

# cache.cmake
set(MY_CACHE_VAR "I am a cached variable" CACHE STRING "Description")
message(STATUS "${MY_CACHE_VAR}")

We have to include the variable type here, which we didn’t have to do before (but we could have) - it helps graphical CMake tools show the correct options. The main difference is the CACHE keyword and the description. If you were to run cmake -L or cmake -LH, you would see all the cached variables and descriptions.

The normal set command only sets the cached variable if it is not already set - this allows you to override cached variables with -D. Try:

cmake -DMY_CACHE_VAR="command line" -P cache.cmake

You can use FORCE to set a cached variable even if it already set; this should not be very common. Since cached variables are global, sometimes they get used as a makeshift global variable - the keyword INTERNAL is identical to STRING FORCE, and hides the variable from listings/GUIs.

Since bool cached variables are so common for builds, there is a shortcut syntax for making one using option:

option(MY_OPTION "On or off" OFF)

Other variables

You can get environment variables with $ENV{name}. You can check to see if an environment variable is defined with if(DEFINED ENV{name}) (notice the missing $).

Properties are a form of variable that is attached to a target; you can use get_property and set_property, or [get_target_properties][] and set_target_properties (stylistic preference) to access and set these. You can see a list of all properties by CMake version; there is no way to get this programmatically.

Handy tip:

Use include(CMakePrintHelpers) to add the useful commands cmake_print_properties and cmake_print_variables to save yourself some typing when debugging variables and properties.

Target properties and variables

You have seen targets; they have properties attached that control their behavior. Many of these properties, such as CXX_EXTENSIONS, have a matching variable that starts with CMAKE_, such as CMAKE_CXX_EXTENSIONS, that will be used to initialize them. So you can using set property on each target by setting a variable before making the targets.

Globbing

There are several commands that help with strings, files, [lists][], and the like. Let’s take a quick look at one of the most interesting: glob.

file(GLOB OUTPUT_VAR *.cxx)

This will make a list of all files that match the pattern and put it into OUTPUT_VAR. You can also use GLOB_RECURSE, which will recurse subdirectories. There are several useful options, which you can look at in the documentation, but one is particularly important: CONFIGURE_DEPENDS (CMake 3.12+).

When you rerun the build step (not the configure step), then unless you setCONFIGURE_DEPENDS, your build tool will not check to see if you have added any new files that now pass the glob. This is the reason poorly written CMake projects often have issues when you are trying to add files; some people are in the habit of rerunning cmake before every build because of this. You shouldn’t ever have to manually reconfigure; the build tool will rerun CMake as needed with this one exception. If you add CONFIGURE_DEPENDS, then most build tools will actually start checking glob too. The classic rule of CMake was “never glob”; the new rule is “never glob, but if you have to, add CONFIGURE_DEPENDS”.

More reading

Key Points

  • Local variables work in this directory or below.

  • Cached variables are stored between runs.

  • You can access environment variables, properties, and more.

  • You can glob to collect files from disk, but it might not always be a good idea.