Retrieve variable from child scope - cmake

I have a third-party CMake package that does some non-trivial work in its own CMakeLists.txt but doesn't set the resulting variables with PARENT_SCOPE so a CMakeLists.txt file that has added the project directory doesn't get to look at the variables.
Tacking a few set commands with PARENT_SCOPE to the end of the package's CMakeLists.txt works fine but is there any trick that would allow a parent scope to extract variables from a child scope?

You can use the get_directory_property command for that purpose:
add_subdirectory(sources)
...
get_directory_property(variableValue DIRECTORY sources DEFINITION variableName)

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.

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 define a cmake macro in a sub_directory that uses the CURRENT_SOURCE_DIR?

What I want to do is to create a CMakeLists.txt that defines a convenience macro to use in parent scope. I can use the macro just fine. However, I used the ${CMAKE_CURRENT_SOURCE_DIR} which is unfortunately not the directory of the CMake script the macro is defined in, but the one it is called from. Is there any way I can change that?
MWE
cmake_minimum_required(VERSION 3.6)
project(git_info CXX)
macro(do_stuff)
message("This CMakeLists.txt file is in ${CMAKE_CURRENT_SOURCE_DIR}")
endmacro()
One ugly way I found was to export variables to the parent scope that contain the path and use that in the macro. But I would prefer to only export the macro definition, if possible, to keep things clean.
Edit:
To clarify a bit. I have one folder with my top-levelCMakeLists.txt and one folder (my_folder) inside with the above MWE CMakeLists.txt. The top-level CMakeLists.txt looks as follows:
cmake_minimum_required(VERSION 3.6)
project(top_project CXX)
add_subdirectory(my_folder)
do_stuff()
You have to transfer CMAKE_CURRENT_LIST_DIR outside the macro into another variable or - in your case - a user defined global property:
set_property(GLOBAL PROPERTY DoStuffPath "${CMAKE_CURRENT_LIST_DIR}")
macro(do_stuff)
get_property(_my_marcros_file GLOBAL PROPERTY DoStuffPath)
message("This CMakeLists.txt file is in ${_my_marcros_file}")
endmacro()
That also works when the macros are defined in a file added with include().
References
In CMake, how can I find the directory of an included file?
What's the CMake syntax to set and use variables?
Use CMAKE_SOURCE_DIR to get a path to outermost parent CMakeLists.txt.

CMake: Function is callable while script, defined it, is no longer included

I have three folders in a location, say A,B and C. I have two cmake files in folder A: FindABC.cmake and UseABC.cmake. The former is for finding the libraries and the latter contains a function, say run_command(). CMakelists.txt in folder B and folder C contains the following lines:
find_package(ABC)
include(UseABC)
run_command()
It works as intended. Now If I comment find_package() and include() in CMakelists of folder C, as far as I know, Cmake should give an error telling unknown command - run_command(). But, the controls goes into the function and executes in unpredictable manner.
How come the control goes to the function when the include line is commented? The root CMakelists that lists the sub-directories does not have any find_package or include lines in it.
Edit:
UseABC.cmake:
set(ABC_COMPILE_DEBUG FALSE)
set(ABC_COMPILE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/abc_gen")
message("USEABC1 - -> " ${ABC_COMPILE_OUTPUT_DIR})
function(run_command)
message("USEABC2 - File recurs -> " ${ABC_COMPILE_OUTPUT_DIR})
file(REMOVE_RECURSE "${ABC_COMPILE_OUTPUT_DIR}")
file(MAKE_DIRECTORY "${ABC_COMPILE_OUTPUT_DIR}")
add_custom_command() #command to be executed
endfunction()
Here, When nothing is commented(find_package and include is not commented in any CMakelists.txt), I get the correct path for the two messages I print.
When I comment include(UseABC) in the second CMakelists.txt, the configuration fails, the first message is not at all printed and the second message gets printed, but does not give the value of the variable. It also deletes all the files in Folder C (but the argument to REMOVE_RECURSE is empty).
If I correctly understand the situation, you have:
CMakeLists.txt:
add_subdirectory(B)
add_subdirectory(C)
B/CMakeLists.txt:
find_package(ABC)
include(UseABC)
In that case run_command function, defined in UseABC.cmake, is accessible in C/CMakeLists.txt, though this script doesn't define it.
In CMake function definitions are global.
By opposite, variable definitions are local to the scope, where they are defined. (Until variables are cached ones, in that case they have global visibility).
That is, variable ABC_COMPILE_DEBUG defined in UseABC.cmake is accessible in
UseABC.cmake script
B/CMakeLists.txt script, because it includes UseABC.cmake one, and include() command doesn't introduce a scope
but it is inaccessible in
CMakeLists.txt script, because add_subdirectory(B) does introduce a scope
C/CMakeLists.txt script
More details about variable's visibility can be found in documentation.

CMake: collecting libraries

I am using CMake to build a simple C++ project, hereafter named P. The structure of P is quite simple:
P/src/
P/src/package1
P/src/packege2
P/src/...
P/src/main-app
I would like to collect the libraries in package1, package2, ... in a variable called P_LIBS.
In a first attempt, I tried to collect the libraries available in package1, package2, ... in the variable called P_LIBS initially set in the src/CMakeLists.txt file. However, the updates to P_LIBS made in the CMakeLists.txt of the subfolders are not propagated to the parent folder.
I would rather not write a list of libraries in the main CMakeLists.txt file. I would rather modify such variable while moving in the directory tree.
After a search on the internet I could not find any valid suggestion. If I look at the various Find files, I only see long listings of libraries in their main CMakeLists.txt file.
Is there a way to do what (I hope) I explained above?
Thanks to sakra's link I was able to 'propagate' names up to the parent folder. However, the names I add to the P_LIBS variable are later interpreted as 'library' names, not as reference to CMake targets. In other words, if
P_LIBS = {a, b}
the 'a' and 'b' are interpreted as the library names, i.e. CMake generates:
gcc [...] -l a -o exe
instead of
gcc [...] /path/to/a.o -o exe
(.o or other extensions)
You are propably constructing the targets list as a string, try to make them a list instead. For example:
# in package1/CMakeLists.txt
set(P_LIBS ${P_LIBS} a b PARENT_SCOPE)