How to get the name of the parent project in CMake? - cmake

Please see the below minimal example:
├───CMakeLists.txt
├───bar
│ ├───CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(foo)
add_subdirectory(bar)
bar/CMakeLists.txt
project(bar)
cmake_path(GET CMAKE_CURRENT_LIST_DIR PARENT_PATH BAR_PARENT_DIR)
# how can I get `foo` given ${BAR_PARENT_DIR}?
# or is there a better way to get `foo`?
The real use case is that originally foo was extracted via ${CMAKE_PROJECT_NAME}, but recently there's a need to make the repo submodule compatible. Once this repo is being used as a submodule, ${CMAKE_PROJECT_NAME} won't be equivalent to foo anymore. Additionally, bar is a submodule of foo, so we aren't allowed to hard code foo into bar/CMakeLists.txt because that would break other repos that are using bar as a submodule.
Is there a way to extract the project name of a CMakeLists.txt from a parent directory?
Edit: I am looking for a solution that will make the below scenario work. Meaning foo is submoduled by another project. e.g.
baz
├───CMakeLists.txt
├───foo
│ ├───CMakeLists.txt
│ ├───bar
│ ├───CMakeLists.txt

Yes, the project name of the immediate parent.
This is not so hard. The project() command always sets the PROJECT_NAME variable when it is called. So the value of that variable just before you call project() is the name of the immediate parent.
There's nothing standard for this, but it's trivial to implement:
cmake_minimum_required(VERSION 3.23)
set(PARENT_PROJECT_NAME "${PROJECT_NAME}")
project(bar)
if (PARENT_PROJECT_NAME)
message(STATUS "Found parent: ${PARENT_PROJECT_NAME}")
else ()
message(STATUS "No parent!")
endif ()

Related

Overrule cmake macro definition in subproject from parent project?

I am using a third-party cmake based project as a git submodule. Lets say we have the following structure:
+ my_project/
- CMakeLists.txt
+ their_project/
- CMakeLists.txt
+ cmake/
- foobar.cmake
their_project/CMakeLists.txt does
include(cmake/foobar.cmake)
Foo(42)
and inside cmake/foobar.cmake we have
macro(Foo)
...
endmacro()
Inside my_project/CMakeLists.txt I do, say,
macro(Foo)
...my definition...
endmacro()
add_subdirectory(their_project)
The result of all this is that their_project still uses their definition of Foo because they redefine it by including cmake/foobar.cmake.
What can I do inside my CMakeLists.txt file to stop this from happening, so that their_project will use my macro?

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: pass library name to grandparent, but only if grandparent exists

A library foo is to be built either as a project of its own, or as part of a larger project bar. For the latter, I found no better solution than line (*):
$ cat foo/lib/CMakeLists.txt
...
set(foo_LIBRARY foo PARENT_SCOPE)
...
$ cat foo/CMakeLists.txt
...
add_subdirectory(lib)
set(cerf_LIBRARY ${cerf_LIBRARY} PARENT_SCOPE) # (*)
...
$ cat bar/CMakeLists.txt
...
add_subdirectory(link-to-foo)
...
Now building bar works. But when building only foo, I get
CMake Warning (dev) at CMakeLists.txt:30 (set):
Cannot set "foo_LIBRARY": current scope has no parent.
Aiming for zero warnings in my projects, I'd need a better solution.

CMake: Is there a way to get a list of imported targets that belong to a package

Sometimes I wish I could get a list of the imported targets that belong to a package. Is there a variable that holds them?
This would allow me to write something like this
find_package(Qt5 CONFIG REQUIRED)
message("Imported Qt5 targets: ${Qt5_IMPORTED_TARGETS}") # speculative code
With my current knowledge I have to rely on the documentation of the package to give me the names of all imported targets. Reading them from a variable or property would be easier.
CMake 3.21 introduced the directory property IMPORTED_TARGETS that can be used to get a list of all imported targets. This can be used to derive a list of targets that were imported by a single find_package() call when it is queried before and after the call to find_package(). The code could look something like this:
...
get_property(importTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
get_property(importTargetsAfter DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
list(REMOVE_ITEM importTargetsAfter ${importTargets})
message("${importTargetsAfter}")
...
Usually it is good enough to only print the list of all imported targets and guess from the names which of them were imported by the package of interest.
Not precisely what you asked for, but for Qt5, one can do:
cmake_minimum_required(VERSION 3.14)
project(so)
find_package(Qt5 COMPONENTS Core Widgets REQUIRED)
get_cmake_property(_variableNames VARIABLES)
foreach(_variableName ${_variableNames})
if(_variableName MATCHES "^Qt5.*LIBRARIES")
message(STATUS "${_variableName}")
message(STATUS "\t${${_variableName}}")
endif()
endforeach()
Example output:
-- Qt5Core_LIBRARIES
-- Qt5::Core
-- Qt5Gui_EGL_LIBRARIES
-- Qt5::Gui_EGL
-- Qt5Gui_LIBRARIES
-- Qt5::Gui
-- Qt5Gui_OPENGL_LIBRARIES
-- Qt5::Gui_GL
-- Qt5Widgets_LIBRARIES
-- Qt5::Widgets
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/build
Caveat with approach: One needs to know the component names.

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