Does scoping behave differently for targets and variables? - cmake

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.

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

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

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.

Retrieve variable from child scope

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)