Modify or set global variable from within Find<package>.cmake file - cmake

I'm striving to understand how to create / modify a global variable (whose prefix is not <PACKAGE>_) from within a Find<package>.cmake file, so that it can be shared among different modules and reused by the global CMakeLists.txt.
Let's say I have a FindMyModule.cmake file:
set (MyModule_include_dirs "...") # <-- This is visible from CMakeLists.txt caller
set (LIBS_INCLUDE_DIRS "${LIBS_INCLUDE_DIRS} ${MyModule_include_dirs}") <-- This is not
In CMakeLists.txt
find_package(MyModule)
message("My Module include dirs: "${MyModule_include_dirs}) # <-- Prints "My Module include dirs: ..."
message("Libs include dirs: " ${LIBS_INCLUDE_DIRS}) # <-- Prints "Libs include dirs: "
Googling and so-ing around, I've tried CACHE, INTERNAL and PARENT_SCOPE but no successful result so far.

I really don't understand why would you want to change a global variable from within a findXYZ.cmake script, as you are tightly coupling other scripts to this script or visa versa.
If I recall correctly, as long as you set a variable as CACHE INTERNAL before you start processing anything it should be available at all time to the following CMake instructions. We could've fixed that easily if you'd provide a minimal reproducible example.
The other options that you have for "GLOBAL variables" is to use set_property() i.e.:
set_property(GLOBAL PROPERTY GLOBAL_LIBS_INCLUDE_DIR "${LIBS_INCLUDE_DIR} ${MyModule_include_dirs}")
And then everywhere else:
get_property(LIBS_INCLUDE_DIR GLOBAL PROPERTY GLOBAL_LIBS_INCLUDE_DIR)
To demonstrate that BOTH WORK:
CMakeLists.txt
cmake_minimum_required (VERSION 3.20)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
project ("GlobalVariables")
set(CACHE_INTERNAL_VARIABLE "HELLO" CACHE INTERNAL "")
set_property(GLOBAL PROPERTY GLOBAL_PROPERTY_VARIABLE "HELLO ")
find_package(MODULE_VARIABLE MODULE)
get_property(GLOBAL_PROPERTY_VARIABLE GLOBAL PROPERTY GLOBAL_PROPERTY_VARIABLE)
message(STATUS ${GLOBAL_PROPERTY_VARIABLE})
message(STATUS ${CACHE_INTERNAL_VARIABLE})
findMODULE_VARIABLE.cmake
set(CACHE_INTERNAL_VARIABLE "${CACHE_INTERNAL_VARIABLE} WORLD" CACHE INTERNAL "")
get_property(TEMP GLOBAL PROPERTY GLOBAL_PROPERTY_VARIABLE)
set_property(GLOBAL PROPERTY GLOBAL_PROPERTY_VARIABLE "${TEMP} WORLD")
Both variables output the same:
-- HELLO WORLD
-- HELLO WORLD

Related

Does scoping behave differently for targets and variables?

I have a parent CMake file that contains,
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
message(STATUS "${MyString}")
endif()
# ...
target_link_libraries(compute_square_root PUBLIC
${EXTRA_LIBS} tutorial_compiler_flags
)
Inside the CMakeLists.txt for MathFunctions contains,
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
set(MyString "Some Text")
message(STATUS "${MyString}")
I am confused about the scoping. It seems here that target_link_libraries can correctly reference the target that was created by the child CMakeLists.txt when running add_subdirectory(MathFunctions) (In particular, add_library(MathFunctions mysqrt.cxx)), however, it cannot correctly access the variables that were also created in the same scope (MyString).
Are targets and variables scoped differently?
Yes, they are scoped differently. Targets are visible at any scope after the point that they have been defined. Regular (non-cache) variables are scoped to directories and functions, and are only visible to script code in the same directory and function scope (same function scope, and same directory level, or subdirectories added by add_subdirectory). To define a variable in the parent directory's scope, you must define it like set(<variable> <value>... PARENT_SCOPE). See the documentation for the set() command for more info.
If you want to "pass" the definition of a variable up several scopes, you must call set(... PARENT_SCOPE) multiple times to go up each scope level.

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()

is it possible to make target shared library file name variable according to environment variable in cmake?

I'm not familar with cmake but in CMakeLists.txt we set the target shared library name like this.
add_library( mylib SHARED ${source_list} )
This generates libmylib.so and other settings in CMakeLists.txt are defined for mylib like
about the mylib
and also we can use shell environment variable to do some selective settings like
target_compile_definitions( mylib PRIVATE -DQQQ -D... )
Also it is possible to use shell environment variable to do some selective things.
if(defined env{MYVAR})
set(CMAKE_C_FLAGS "-g -DXYZ")
else()
set(CMAKE_C_FLAGS "-DXYZ")
endif()
I would be happy if I could set the target shared library name as a variable according to the environment variable and use that selected name variable as the shared library name in all other settings. In other words, is it possible to do things like below?
if (defined ENV{FOR_QEMU})
set_name(target_name "simlib_qemu")
else ()
set_name(target_name "simlib")
endif ()
add_library(target_name SHARED ${source_list} )
target_compile_definitions( target_name PRIVATE -DQQQ -D... )
...
You can set the output name of a target to anything you like via:
set_target_properties(target_name PROPERTIES OUTPUT_NAME "whatever")
Then instead of libtarget_name.so, you'll get libwhatever.so. You would continue to refer to the target as target_name in your CMakeLists.txt.
However, since this will only work during configure time anyway, I strongly urge you to use a normal CMake variable instead. You may initialize it from the environment if it is not set, like so:
option(FOR_QEMU "Enable if building with Qemu support" "$ENV{FOR_QEMU}")
add_library(simlib SHARED ${source_list})
target_compile_definitions(simlib PRIVATE -DQQQ -D...)
if (FOR_QEMU)
set_target_properties(target_name PROPERTIES OUTPUT_NAME "simlib_qemu")
endif ()
This way, the CMake variable FOR_QEMU is the de-facto control and it is initialized on the first execution if the matching env-var is set. It will also appear with documentation in the cache, so other developers may query the build system directly for all its configuration points. Bear in mind: CMake is not Make and reading from the environment on every configure is a surprising behavior and generally bad practice.

Declare an empty list, and update it in functions [duplicate]

I'm trying to create a global list and I want it appended in a macro.
Here is my setup:
project
\__. CMakeLists.txt
\__. level1
\__. CMakeLists.txt
\__. level2a
\__. CMakeLists.txt
\__. level2b
\__. CMakeLists.txt
Here is my top level CMakeLists.txt :
cmake_minimum_required(VERSION 2.8)
macro(listappend var)
list(APPEND MY_GLOBAL_LIST "${var}")
message(STATUS "LIST IN MACRO SCOPE: ${MY_GLOBAL_LIST}")
endmacro(listappend)
set(MY_GLOBAL_LIST "")
add_subdirectory(level1)
message(STATUS "LIST: ${MY_GLOBAL_LIST}")
# call something here with the global list
level1 CMakeLists.txt simply do two add_subdirectory().
level2 CMakeLists.txt is as follows:
listappend("test2a")
And finally, here is my output :
[lz#mac 17:15:14] ~/tmp/cmake/build$ cmake ..
-- LIST IN MACRO SCOPE: test2a
-- LIST IN MACRO SCOPE: test2b
-- LIST:
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lz/tmp/cmake/build
I'm looking for a way to have a Global list appended inside the scope of the macro, without having to give the global list variable as parameter of the macro.
I'm not sure if it's possible.
I also tried CACHE INTERNAL flags but it didn't help. I don't really how to handle this.
Thanks for any help :)
CMake GLOBAL property is a nice way for implement global list which is modified at different levels:
# Describe property
define_property(GLOBAL PROPERTY MY_GLOBAL_LIST
BRIEF_DOCS "Global list of elements"
FULL_DOCS "Global list of elements")
# Initialize property
set_property(GLOBAL PROPERTY MY_GLOBAL_LIST "")
# Macro for add values into the list
macro(listappend var)
set_property(GLOBAL APPEND PROPERTY MY_GLOBAL_LIST "${var}")
endmacro(listappend)
# Do something
add_subdirectory(level1)
# Read list content
get_property(my_list_content GLOBAL PROPERTY MY_GLOBAL_LIST)
message(STATUS "LIST: ${my_list_content}")

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