set a cmake variable if it is not changed by the user - cmake

How can i (re)set cmake variable only when it is not changed by the user?
I have this variables:
set(DIR "testdir" CACHE PATH "main directory")
set(SUBDIR ${DIR}/subdir CACHE PATH "subdirectory")
On the first run the variables are initialized to testdir and testdir/subdir.
When the user changed DIR and reruns cmake without changing SUBDIR, i would like to generate a new SUBDIR path, while i want to keep the SUBDIR path, when the user changed it.
So SUBDIR should be set to the new default value based on the new value of DIR, if DIR was changed and SUBDIR was never changed before.

Turning my comment into an answer
You could use MODIFIED cache variable property, but the documentation says
Do not set or get.
Maybe a better approach would be to check the modification with if statements:
set(DIR "testdir" CACHE PATH "main directory")
if (NOT DEFINED SUBDIR OR SUBDIR MATCHES "/subdir$")
set(SUBDIR "${DIR}/subdir" CACHE PATH "subdirectory" FORCE)
endif()
Or you just don't put DIR inside SUBDIR and put the details into the description:
set(SUBDIR "subdir" CACHE PATH "subdirectory of main directory (see DIR)")

Along with cache variable SUBDIR visible to the user, you may store another cache variable, say SUBDIR_old, which contains the last value of SUBDIR and marked as INTERNAL (that is not intended to be modified by the user).
Next launching of cmake you may compare values of SUBDIR and SUBDIR_old, and if they differ, then user has modified SUBDIR:
if(NOT DEFINED SUBDIR_old OR (SUBDIR EQUAL SUBDIR_old))
# User haven't changed SUBDIR since previous configuration. Rewrite it.
set(SUBDIR <new-value> CACHE PATH "<documentation>" FORCE)
endif()
# Store current value in the "shadow" variable unconditionally.
set(SUBDIR_old ${SUBDIR} CACHE INTERNAL "Copy of SUBDIR")
Problem with that approach that user cannot say:
I have checked value of SUBDIR and found it already correct.
Without modification we assume that user don't bother about variable's value.

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.

Read a file or print a message in CMake

I'm trying to read a file's content and set a variable on the condition whether a file exists relative to the CMakeLists.txt script file. For example, I want to conditionally set an environment variable with the content of a file that resides on disk, and if it's not there I want to print a helpful message.
if (EXISTS pkgconfig-environment)
file(READ pkgconfig-environment LOCAL_PKG_CONFIG_PATH)
set(ENV{PKG_CONFIG_PATH} ${LOCAL_PKG_CONFIG_PATH})
else()
message("
I hope you know what you're doing with your pkg-config.
")
endif ()
The logic above never detects the file pkgconfig-environment, and it instead always prints the message. The file can be read into a cmake variable, but only if it exists.
There are two problems: first, file(READ ...) will fail the build sometimes because the file doesn't exist (I don't care if it's a directory and it fails. That's not my use case). Second, the parameter expected in the call if(EXISTS path) should probably be an absolute path, but I wanted the file to be tested for existence relative to the CMakeLists.txt script file.
Given how clearly the documentation states that exists-checks are supposed to be absolute paths, it leads me to think there's some way to determine the absolute path of a file from a relative path near the CMakeLists.txt.
To get the full path to the directory containing the current CMakeLists.txt file, use ${CMAKE_CURRENT_LIST_DIR}:
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/pkgconfig-environment)
file(READ ${CMAKE_CURRENT_LIST_DIR}/pkgconfig-environment LOCAL_PKG_CONFIG_PATH)
set(ENV{PKG_CONFIG_PATH} ${LOCAL_PKG_CONFIG_PATH})
else()
message("
I hope you know what you're doing with your pkg-config.
")
endif ()

CMake: show all modified variables

I would like to have a command or option to list all the modified cache variables of the current build configuration. While cmake -L[AH] is nice, it is also quite overwhelming and doesn't show which are non-default values.
There seems to be a variable property MODIFIED that sounds exactly like what I'm looking for - but the documentation is not very reassuring:
Internal management property. Do not set or get.
This is an internal cache entry property managed by CMake to track interactive user modification of entries. Ignore it.
This question also didn't help: CMAKE: Print out all accessible variables in a script
There are so many ways you could change or initialize variables in CMake (command line, environment variables, script files, etc.) that you won't be able to cover them all.
I just came up with the following script that covers the command line switches. Put the following file in your CMake project's root folder and you get the modified variables printed:
PreLoad.cmake
set(_file "${CMAKE_BINARY_DIR}/UserModifiedVars.txt")
get_directory_property(_vars CACHE_VARIABLES)
list(FIND _vars "CMAKE_BACKWARDS_COMPATIBILITY" _idx)
if (_idx EQUAL -1)
list(REMOVE_ITEM _vars "CMAKE_COMMAND" "CMAKE_CPACK_COMMAND" "CMAKE_CTEST_COMMAND" "CMAKE_ROOT")
file(WRITE "${_file}" "${_vars}")
else()
file(READ "${_file}" _vars)
endif()
foreach(_var IN LISTS _vars)
message(STATUS "User modified ${_var} = ${${_var}}")
endforeach()
This will load before anything else and therefore can relatively easily identify the user modified variables and store them into a file for later reference.
The CMAKE_BACKWARDS_COMPATIBILITY is a cached variable set by CMake at the end of a configuration run and therefor is used here to identify an already configured CMake project.
Reference
CMake: In which Order are Files parsed (Cache, Toolchain, …)?

How to change the reference path for get_filename_component in cmake?

I'm using get_filename_component in cmake to get the absolute path of a possibly relative path given in a variable.
And I want to do an out-of-tree/out-of-source-build.
It seems to me that get_filename_component is using CMAKE_SOURCE_DIR as reference-path.
Is there a way to change that or to workaround it?
One way I tried is to prefix my potential relative path with ${CMAKE_BINARY_DIR} but that stops working of the path given is not relative.
Assuming your relative path is always relative to CMAKE_BINARY_DIR, then you can handle this pretty easily using if(IS_ABSOLUTE ...):
if(NOT IS_ABSOLUTE ${MyPath})
set(MyAbsPath ${CMAKE_BINARY_DIR}/${MyPath})
endif()
If the subject file or dir exists at CMake run time, then you can always do a find_file call, passing the possible NAMES and PATHS. If the file exists and is found, the resulting variable will hold the full path to the file.
Or you can use the if(EXISTS ...) signature of if to check for the existence or not of the given file.

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

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.