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

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}")

Related

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

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

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 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.

Variables set with PARENT_SCOPE are empty in the corresponding child scope. Why?

Consider the following minimal example:
.
├── bar
│   └── CMakeLists.txt
└── CMakeLists.txt
where ./CMakeLists.txt is
project( foo )
cmake_minimum_required( VERSION 2.8 )
set( FOO "Exists in both, parent AND in child scope." )
add_subdirectory( bar )
message( STATUS "Variable BAR in ./ = ${BAR}" )
message( STATUS "Variable FOO in ./ = ${FOO}" )
and ./bar/CMakeLists.txt is
set( BAR "Exists in parent scope only." PARENT_SCOPE )
message( STATUS "Variable BAR in ./bar/ = ${BAR}" )
The relevant part of the output of cmake is this:
...
-- Variable BAR in ./bar/ =
-- Variable FOO in ./bar/ = Exists in both, parent AND in child scope.
-- Variable BAR in ./ = Exists in parent scope only.
-- Variable FOO in ./ = Exists in both, parent AND in child scope.
...
Since the variable BAR is placed into the parent scope I would expect it to be available in the current child scope as well (and in those that follow) -- just like the variable FOO, which is defined the parent scope to begin with. But as can be seen in the above lines the
variable BAR is empty in ./bar/CMakeLists.txt, which lead me to
the following questions:
Why is the modified parent scope not immediately accessible in the child
scope, ./bar/? Can this be mitigated? If yes, how? And if no, what is a
work-around? Or am I completely missing something obvious?
Context: my project consists of several executables and libraries. For a
library, e.g. bar, I'd like to set a variable bar_INCLUDE_DIR which
is added to the include paths of any depending executable, i.e. target_include_directories( my_target PUBLIC bar_INCLUDE_DIR ).
I do not see anything that is not consistent with the SET command documentation
If PARENT_SCOPE is present, the variable will be set in the scope above the current scope. Each new directory or function creates a new scope. This command will set the value of a variable into the parent directory or calling function (whichever is applicable to the case at hand).
./bar/CMakeLists.txt
set( BAR "This is bar." PARENT_SCOPE ) #<-- Variable is set only in the PARENT scope
message( STATUS "Variable BAR in ./bar/ = ${BAR}" ) #<--- Still undefined/empty
You can always do:
set( BAR "This is bar." ) #<-- set in this scope
set( BAR ${BAR} PARENT_SCOPE ) #<-- set in the parent scope too
Grep for PARENT_SCOPE in the delivered modules in your installation, for example FindGTK2
if(GTK2_${_var}_FOUND)
set(GTK2_LIBRARIES ${GTK2_LIBRARIES} ${GTK2_${_var}_LIBRARY})
set(GTK2_LIBRARIES ${GTK2_LIBRARIES} PARENT_SCOPE)
endif()
Peter explained well the reason for this behaviour.
A workaround I usually use in this case is to set a cached variable, which will be visible everywhere:
set(BAR "Visible everywhere"
CACHE INTERNAL ""
)
INTERNAL is to make it not visible from cmake-gui. INTERNAL implies FORCE, making sure it gets updated if you change something for example in your folder structure. The empty string is a description string, that you might want to fill if you believe it's necessary.
Note, though, that the correct approach is attaching properties to targets whenever possible, like using target_incude_directories, and propagate them to other targets by setting dependencies.
Context: my project consists of several executables and libraries. For a library, e.g. bar, I'd like to set a variable bar_INCLUDE_DIR which is added to the include paths of any depending executable.
There is a much better way to do this than to set variables in the parent scope. CMake allows a target to specify include directories, preprocessor symbols etc. that depending targets can use. In your case, you can use target_include_directories.
For example:
target_include_directories(my_target PUBLIC my_inc_dir)
If you only need to set a variable both in the local and parent scope, a macro can help reduce duplication:
macro(set_local_and_parent NAME VALUE)
set(${ARGV0} ${ARGV1})
set(${ARGV0} ${ARGV1} PARENT_SCOPE)
endmacro()
Each variable in the cmake has it's own scope so it is dangerous use case where a variable automatically propagates in a child context, because it can interfere with it from a parent scope!
But you can set just another variable in a child scope to test it later instead of rereading a parent one:
./bar/CMakeLists.txt:
set( BAR "Exists in parent scope only." PARENT_SCOPE )
set( _somerandomid_BAR "Exists in child scope only.")
message( STATUS "Variable BAR in ./bar/ = ${_somerandomid_BAR}" )
Now, if you have loops in your code, then you can test both variables:
foreach(...)
...
# read a variable token name and token value, for example, from a configuration file
set(my_var_name_token ...)
set(my_var_value_token ...)
...
# parse a variable name and value tokens into a real name and value
set(my_var_name ...)
set(my_var_value ...)
...
if (DEFINED ${my_var_name})
if (DEFINED local_${my_var_name})
message("has been defined before and was resetted");
else()
message("has been defined before and was not resetted");
endif()
else()
if (DEFINED local_${my_var_name})
message("has not been defined before and was setted");
else()
message("has not been defined before and was not touched");
endif()
endif()
...
# sets parsed name and value into cmake context
set(${my_var_name} "..." PARENT_SCOPE)
# Do save all values has been setting from this function to differently compare and
# validate them versus already existed before:
# 1. If has been set before, then must be set to the same value, otherwise - error
# 2. If has not been set before, then should set only once, otherwise - ignore new
# value (a constant variable behaviour)
set(local_${my_var_name} "...")
...
endforeach()

Add dependencies to a custom target

I need a way to add additional dependencies to a custom target. I have a macro which adds resource files to a particular project, used like this:
ADD_RESOURCES( ${TARGET} some/path pattern1 pattern2 )
ADD_RESOURCES( ${TARGET} another/path pattern1 )
I create a target called ${TARGET}_ASSETS and would like to attach the generation of all these resources to the one target. add_dependencies however only accepts other targets. So if I produce a file via a add_custom_command I cannot use that as a dependency.
The workaround might be to just create a new custom taget for each call to ADD_RESOURCES and then attached that to the ASSETS target. Each target requires a unique name however, and these is no way to generate this unique name from the parameters of ADD_RESOURCES.
One work-around is to postpone the generation of the ${target}_ASSETS custom targets until all dependencies have been set up with calls to ADD_RESOURCES.
Instead of immediately adding the dependencies to the custom target, the macro ADD_RESOURCES has to record the dependencies in a global variable, whose name depends on the target:
macro (ADD_RESOURCES _targetName)
set (_dependencies ${ARGN})
...
# record depencies in a target dependency variable
if (DEFINED ${_targetName}_Dependencies)
list (APPEND ${_targetName}_Dependencies ${_dependencies})
else()
set (${_targetName}_Dependencies ${_dependencies})
endif()
endmacro()
Then add another helper macro which determines all defined target dependency variables through reflection and sets up a custom target for each target:
macro (SETUP_ASSETS_TARGETS)
get_cmake_property(_vars VARIABLES)
foreach (_var ${_vars})
if (_var MATCHES "(.+)_Dependencies")
set (_targetName ${CMAKE_MATCH_1})
set (_targetDependencies ${${_var}})
message("${_targetName} depends on ${_targetDependencies}")
add_custom_target(${_targetName}_ASSETS DEPENDS ${_targetDependencies})
endif()
endforeach()
endmacro()
In your CMakeLists.txt add all necessary dependencies with calls to ADD_RESOURCES, then call the SETUP_ASSETS_TARGETS macro to have all custom targets defined.
ADD_RESOURCES( target1 some/path pattern1 pattern2 )
ADD_RESOURCES( target1 another/path pattern1 )
ADD_RESOURCES( target2 foo/bar pattern1 )
...
...
SETUP_ASSETS_TARGETS()
I know this is a late answer, but i post my solution for everyone who searches for this problem:
function(target_resources THIS)
if (NOT TARGET ${THIS}_res)
# this is just a pseudo command which can be appended later
add_custom_command(OUTPUT ${THIS}_dep COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR})
# add a dependency with a target, as a command itself can not be a dependency
add_custom_target(${THIS}_res DEPENDS ${THIS}_dep)
add_dependencies(${THIS} ${THIS}_res)
endif ()
get_target_property(RUNTIME_OUTPUT_DIRECTORY ${THIS} RUNTIME_OUTPUT_DIRECTORY)
foreach (RES_FILE IN LISTS ARGN)
if (IS_ABSOLUTE ${RES_FILE})
file(RELATIVE_PATH PATH ${CMAKE_CURRENT_SOURCE_DIR} ${RES_FILE})
endif ()
# append the resource command with our resource
add_custom_command(OUTPUT ${THIS}_dep
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${RES_FILE}
${RUNTIME_OUTPUT_DIRECTORY}/${RES_FILE}
APPEND)
endforeach ()
endfunction()
The benefit of this solution is that is does not rely on global variables nor the need to invoke a setup macro.