CMake: show all modified variables - cmake

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, …)?

Related

Debug cmake, where is variable defined?

I have very complex CMake project. Where variables are often defined like set("${scope}_${variable_name}" value..) or other complex way.
I need to find where a variable is defined, where it obtains a value.
I tried variable_watch at the beginning of the cmake, but that only gives me READ_ACCESS so I guess that setting the variable is not covered in variable_watch mechanics.
I need to find out where that variable is set, but I run out of ideas. Variable watch does not help, search sources fails due to complex variable definitions.
You can add on top of the CMakeLists:
macro(set name)
message(STATUS "defninng ${name}")
_set(${name} ${ARGV})
endmacro()
set(a b)
and print CMAKE_CURRENT_LIST_* variables.
You could do this without modifying the cmake files: Simply use grep (or windows equivalent) in combination with running the cmake configuration with the --trace-expand option.
Using this option for example
message("TGT_TYPE = ${TGT_TYPE}")
could result in console output like
/some/path/CMakeLists.txt(71): message(TGT_TYPE = UTILITY )
TGT_TYPE = UTILITY
so
cmake --trace-expand build_dir | grep -P "[sS][eE][tT]\s*\(\s*VARIABLE_NAME\s"
should provide you with the line containing the logic to set VARIABLE_NAME in the project you've set up in the directory build_dir.

Expand variable name containing a generator expression in cmake

In the build process, I set directories where I gather the build output of different sub-projects. The directories are set as :
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/../build/bin/debug" )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/../build/bin/release" )
Now, I'd like to copy some files (a directory of qt plugins) to that directory dependent on the configuration which it is built for.
I tried:
# copy qt plugins
add_custom_command( TARGET mytarget POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins"
COMMAND_EXPAND_LISTS)
thus, I try to build a string that equals the variable name and then try to expand that as described here: CMake interpret string as variable. In other words: I would like to have a generator expression that evaluates to the content of CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTOR_RELEASE dependent on the current build configuration.
However running cmake with the statement above results in an error:
"CMakeLists.txt:112: error: Syntax error in cmake code at [..] when parsing string ${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins Invalid character ('<') in a variable name: '$'
So my question is, how can I use a generator-expression to access the corresponding variable? (Bonus question: is there another/better way to achieve the same goal?)
So my question is, how can I use a generator-expression to access the corresponding variable?
You cannot. There is currently (CMake <=3.23) no way to expand a variable whose name is determined by the value of a generator expression.
Bonus question: is there another/better way to achieve the same goal?
Yes, and you are almost there! You can use $<TARGET_FILE_DIR:...>:
add_custom_command(
TARGET mytarget POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"$<TARGET_FILE_DIR:mytarget>/plugins"
VERBATIM
)
This works because TARGET_FILE_DIR evaluates to the actual directory containing the executable or library file for mytarget, no matter the active configuration, property values, etc.
Docs: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_FILE_DIR
CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> is already relative to the binary directory so you should not try to compute the binary directory in its definition. Also, it supports generator expressions. Thus, the following will be much more robust:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin/$<LOWER_CASE:$<CONFIG>>"
CACHE STRING "Common output directory for runtime artifacts")
This has a bunch of concrete benefits:
No need to set CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE
This will work for MinSizeRel and RelWithDebInfo, plus any custom configurations one might add down the line.
Since it's defined as a cache variable, it can be overridden for debugging / working around name clashes, etc.
A bit more context for (3): most CMAKE_* variables are intended to be either read-only or user-configurable (i.e. at the command line, from the GUI, etc.). Overriding their defaults via set(CACHE) is a polite compromise. A notable exception to this rule is the collection of Qt codegen flags (CMAKE_AUTO{MOC,RCC,UIC}). These must typically be set for the build to produce usable binaries.

How can cmake detect misspelled variable names on command line?

My CMakeLists.txt can take variables and values when the user specifies them on the command line in the usual form -Dname=value. E.g.
% cmake -DmyVariable=someValue ..
How can CMakeLists.txt detect variables that aren’t actually relevant, e.g. in case the user mispells them:
% cmake -Dmyxvarble=someValue ..
For example, can CMakeLists.txt process each defined variable on the command line sequentially, thereby spotting misspelled variable names?
I’m running cmake version 3.18.0-rc2. Thanks!
You could query the cache entries of the toplevel dir and match against patterns of expected entries. Note though that this is not easy to maintain, since functionality like find_package relies on cache variables.
set(CACHE_VARIABLE_WHITELIST
MyProject_BINARY_DIR
MyProject_IS_TOP_LEVEL
MyProject_SOURCE_DIR
...
)
get_directory_property(CACHE_VARS DIRECTORY ${CMAKE_SOURCE_DIR} CACHE_VARIABLES)
foreach(CACHE_VAR IN LISTS CACHE_VARS)
# fatal error for any non-advanced cache variable
# not in the whitelist and not starting with CMAKE_
get_property(IS_ADVANCED CACHE ${CACHE_VAR} PROPERTY ADVANCED)
if (NOT IS_ADVANCED AND NOT CACHE_VAR MATCHES "^CMAKE_.*" AND NOT CACHE_VAR IN_LIST CACHE_VARIABLE_WHITELIST)
message(FATAL_ERROR "Unexpected cache variable set: ${CACHE_VAR}")
endif()
endforeach()

CMake: How to specify a different link script for each target

I have multiple targets that are being made in a CMake project. Each target has a different linkscript (LD file). How do I write the CMakeLists.txt file to make this happen? This is for an embedded project with C/C++/ASM files.
This is what I have so far. The problem is that LINKER_SCRIPT is defined globally and not per-target.
# add alpha target
add_executable(alpha.elf ${SOURCES})
target_compile_definitions(alpha.elf PUBLIC -DALPHA_DEFINED)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Linker/alpha.ld)
# add beta target
add_executable(beta.elf ${SOURCES})
target_compile_definitions(beta.elf PUBLIC -DBETA_DEFINED)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Linker/beta.ld)
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -T ${LINKER_SCRIPT}")
The problem is that LINKER_SCRIPT gets overwritten and the last definition is the one used. How can I make this work?
I have tried to define per-target variable using the following, however the output is not as expected. The file gets compiled, however things are not where they should be. For example, the generated HEX file does not start at 0x08000000 which is where it should be defined per the LD file.
set_target_properties(alpha.elf PROPERTIES
CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Map=${PROJECT_BINARY_DIR}/alpha.elf.map -T ${CMAKE_SOURCE_DIR}/Linker/alpha.ld")
The variable LINKER_SCRIPT is just being over-ridden the second time you set it to a value. It doesn't have global scope but sub-directory / function scope. But like any variable it takes on its latest value.
CMAKE_EXE_LINKER_FLAGS is being used for both targets in this case. Splitting this into separate sub-directories may work but I've never tried it.
There are no per-target variables that I am aware of. set_target_properties is used to set properties. Some properties are known to CMake, but the property CMAKE_EXE_LINKER_FLAGS is not one of them. It should just be ignored when generating the build files.
Try using target_link_options to set per-target property LINK_OPTIONS which is known and the option will show up when linking the executable.
For example target_link_options(alpha.elf PRIVATE -T${CMAKE_SOURCE_DIR}/Linker/alpha.ld). I haven't used it with options that have a space in them so that might be a problem.
To re-link if the linker script changes then refer to this answer:
https://stackoverflow.com/a/42138375/1028434

How to use CHECK_INCLUDE_FILES macro in cmake?

I need link my program against Kerberos authentication library (gssapi_krb5) with the corresponding headers gssapi/gssapi.h and gssapi/gssapi_krb5.h included in the source file.
Currently, the compilation will continue if headers are absent and stop with a compile time error saying header files not found.
What I want to implement in the cmake file is to check the existence of the header file and stop compiling if not found.
I add the following code into my CMakeList.txt file.
INCLUDE(CheckIncludeFiles)
CHECK_INCLUDE_FILES(gssapi/gssapi.h;gssapi/gssapi_krb5.h HAVE_KRB_HEADERS)
IF (NOT HAVE_KRB_HEADERS)
RETURN()
ENDIF (NOT HAVE_KRB_HEADERS)
But it still does not act as I expected.
I would like the following lines:
-- Looking for gssapi/gssapi.h - found
-- Looking for gssapi/gssapi_krb5.h - not found
but fail.
Also, the variable HAVE_KRB_HEADERS is empty when output with message macro.
Compile continues until the error described above occurs.
I read somewhere on the Web, this may be because CMake cache.
I'm very new to CMake and not quite clear with that concept.
My CMake version is 2.6.
How could I make this code work? Thank you!
I can't say I'm a huge fan of CheckIncludeFiles because of its difficulty to get right. In principal it's good - it actually creates tiny c files which #include the requested headers and tries to compile them, but it seems to be too easy to get wrong.
I generally prefer just using find_path and/or find_file for this job. This doesn't check the contents of any files found, but usually if you find the required header, its contents are good!
I would use find_path if I needed to know the folder where the header lived. This would usually be because I need to check for other files in the same folder (as in your case), or more commonly because I need to add the folder to an include_directories call.
find_file yields the full path to the file (if found). For headers, normally I don't need the path elsewhere in the CMakeLists - it's just used immediately after the find_file to check the file was actually found.
So, here's how I'd go about checking for "gssapi/gssapi.h" and "gssapi/gssapi_krb5.h"
find_path(GssApiIncludes gssapi.h PATHS <list of folders you'd expect to find it in>)
if(NOT GssApiIncludes)
message(FATAL_ERROR "Can't find folder containing gssapi.h")
endif()
find_file(GssKrb gssapi_krb5.h PATHS ${GssApiIncludes} NO_DEFAULT_PATH)
if(NOT GssKrb)
message(FATAL_ERROR "Can't find gssapi_krb5.h in ${GssApiIncludes}")
endif()
If you do this, then if required you could add
include_directories(${GssApiIncludes})
so that in your source code you can do
#include "gssapi.h"
#include "gssapi_krb5.h"
For anyone who has to work with CHECK_INCLUDE_FILES, the documentation lists a variable called CMAKE_REQUIRED_INCLUDES where you can set additional include paths apart from the default headers.
In a CMake file:
LIST(APPEND CMAKE_REQUIRED_INCLUDES "gssapi")
From the command line:
cmake . --DCMAKE_REQUIRED_INCLUDES="gssapi"
If all else fails, you can set the -I<dir> flag manually. However, this is not recommended as it not portable across compilers.
# note the extra space before `-I`
STRING(APPEND CMAKE_C_FLAGS " -Igssapi")
STRING(APPEND CMAKE_CXX_FLAGS " -Igssapi") # for C++
Also note that C++ headers have a different macro called CheckIncludeFileCXX.