cmakecmakelists-optionscmake-language

Is there a rule to know which CMake variables are settable with -D?


I'm learning CMake and am trying to get my head around the settable variables.

I've heard CMake described as a stateful class, whose "member variables" you manipulate using the various commands in CMakeLists.txt (sorry, I cannot remember where I heard/read this description).

From the CMake man-page:

-L[A][H]
List non-advanced cached variables.
List CACHE variables will run CMake and list all the variables from the CMake CACHE that are not marked as INTERNAL or ADVANCED. This will effectively display current CMake settings, which can then be changed with -D option. Changing some of the variables may result in more variables being created. If A is specified, then it will display also advanced variables. If H is specified, it will also display help for each variable.

So my understanding is that cmake -L[A][H] lists the CMake variables that are settable with -D arguments to make:

$ cmake -LA -B build 2>/dev/null
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/dev/learning-cmake/lesson_23/build
-- Cache values
CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line
CMAKE_AR:FILEPATH=/usr/bin/ar
CMAKE_BUILD_TYPE:STRING=
CMAKE_COLOR_MAKEFILE:BOOL=ON
CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++
CMAKE_CXX_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-11
CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-11
CMAKE_CXX_FLAGS:STRING=
CMAKE_CXX_FLAGS_DEBUG:STRING=-g
CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND
CMAKE_EXE_LINKER_FLAGS:STRING=
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
CMAKE_INSTALL_PREFIX:PATH=/usr/local
CMAKE_LINKER:FILEPATH=/usr/bin/ld
CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/gmake
CMAKE_MODULE_LINKER_FLAGS:STRING=
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
CMAKE_NM:FILEPATH=/usr/bin/nm
CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy
CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump
CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib
CMAKE_READELF:FILEPATH=/usr/bin/readelf
CMAKE_SHARED_LINKER_FLAGS:STRING=
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
CMAKE_SKIP_RPATH:BOOL=NO
CMAKE_STATIC_LINKER_FLAGS:STRING=
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
CMAKE_STRIP:FILEPATH=/usr/bin/strip
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE

So, for example, I could change the C++ compiler flags from the command-line with something along the lines of cmake -DCMAKE_CXX_FLAGS=....

Now, I recently learned about the variable CMAKE_RUNTIME_OUTPUT_DIRECTORY, which is documented (here) but does not show up in the list of command line-settable variables.
My learning material states that CMAKE_RUNTIME_OUTPUT_DIRECTORY is used as a convenience to place all compiled binaries (e.g. executables and shared libraries) in the same directory, which is done on Windows to place shared libraries in the same directory as their dependent executable, to avoid the need to set %PATH% so that the executable looks in necessary directories for its needed shared libraries.

Looking at the list of command line-settable variables, it's not clear to me why CMAKE_RUNTIME_OUTPUT_DIRECTORY shouldn't be in that list (as opposed to some variables like CMAKE_CURRENT_SOURCE_DIR, which seems obviously a should-not-be-user-manipulated).

My question is: is there a rule of thumb/heuristic/etc. by which one can intuit which variables are command line-settable (and why)? I'm trying to build a mental model of CMake, and I'd like to understand what seems to be a "division" or "classification" to the CMake's various variables.

If I understand correctly, the variables that are command-line settable can also be set from within a CMakeLists.txt; ideally I'd like to reach a point where my mental model of CMake helps me intuit when to set a variable from the command line versus when to do it from inside a CMakeLists.txt.


Solution

  • CMake has multiple kinds of variables, the most common being cache variables and block-scoped variables. The block-scoped variables are only visible in the current CMakeLists.txt file and anything included by that (either through add_subdirectory or include calls) after the set call that sets the variable [0]. The cache variables are visible everywhere once set.

    Cache variables, as the name suggests, are cached between calls of cmake, and are stored in a CMakeCache.txt file in the build directory. By default, CMake will populate some of them, e.g. the path of the source tree, the path to the compiler and other parts of the toolchain. What cmake -L[A][H] prints is the set of cache variables currently set for the build in that build directory. If your CMake project doesn't set any additional cache variables itself or through the use of other CMake functionality such as find_package, find_library, etc. what cmake -L[A][H] will print is just the set of variables that CMake will set by default, but this is not a complete list of all settable variables.

    Any use of ${VARIABLE_NAME} in CMake will use the value of the block-scoped variable with name VARIABLE_NAME if one exists, and if it doesn't will try to use the value of a cache variable with the name VARIABLE_NAME. If that one also doesn't exists ${VARIABLE_NAME} will evaluate to an empty string.

    There are a bunch of CMake variables that influence the behavior of other commands, but aren't set by default. If the project doesn't manually specify them either, you can then change the behavior through a cache variable of the same name. This is what happened when you manually specified -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=<whatever> on the command line. Since neither CMake itself nor the project you tested this with set a scoped variable of that name, setting CMAKE_RUNTIME_OUTPUT_DIRECTORY on the command-line had an effect. If the project itself had set it at block-scope to e.g. ${PROJECT_BINARY_DIR}/bin (e.g. in their top-level CMakeLists.txt) then setting it on the command line wouldn't have had any effect.

    CMAKE_CURRENT_SOURCE_DIR can also be set on the command line. But because CMake automatically sets it to the directory of the file it currently processes whenever it starts processing a new file it just won't be visible when evaluating ${CMAKE_CURRENT_SOURCE_DIR}, as there will always be a non-cache variable with that name. However, CMake also allows to use $CACHE{VARIABLE_NAME} to only look for cache variables and ignore variables of the same name in the current scope. So you could observe that someone has set CMAKE_CURRENT_SOURCE_DIR as a cache variable (on the command-line or through other means) by evaluating $CACHE{CMAKE_CURRENT_SOURCE_DIR}.

    To summarize: There is no complete list of variables that can be set on the command-line, but it depends on the project you're building. Most projects will define cache variables (mostly through the option command) to allow the user to configure certain parts of their project, e.g. which implementation to use if there are multiple, or whether to also build documentation, so these necessarily can't be known without looking at the project itself.

    For the variables CMake defines there is a list of known variables. It is mostly a matter of best practices and convention which of these are set as block-scoped variable and which of these should be set as cache variables so that users can override them. Unfortunately, as best practices evolve not everyone follows suit, so this can lead to some issues. For example, many projects still set CMAKE_CXX_STANDARD as a block-scoped variable in their CMakeLists.txt because at the time they started their project they needed a more modern standard than the default. However, this now makes it impossible for a top-level project to force building it with an even newer standard version.

    [0] There is a way to set the value of a block-scoped variable in the scope of the parent instead, so it will also be visible to sibling CMakeLists.txt files included later, but this functionality is mostly used in functions.