CMake List of all Project Targets

I have a top level project CMakeLists.txt file with multiple CMakeLists.txt below added with add_subdirectory that define targets.

Is there a way to list all of the targets from my top level project?

1 Like

There is the BUILDSYSTEM_TARGETS directory property which was introduced in CMake 3.7. See https://cmake.org/cmake/help/latest/prop_dir/BUILDSYSTEM_TARGETS.html for more details.

Thanks for the response!

Would I then have to iterate all directories/subdirectories and get that property on each one?

Exactly.

You can use the SUBDIRECTORIES directory property to make this generic.

Does CMake not support recursive functions?

I have something like

function(getAllSubdirs dir dirs)
    message("called with dir (${dir}) and dirs (${dirs})")
    set(_dirs "")
    # get subdirectories for dir
    get_property(subdirs DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
    # iterate any found subdirectories
    foreach(subdir ${subdirs})
        # append each sub directory
        list(APPEND _dirs ${subdir})
        set(${dirs} _dirs PARENT_SCOPE)
        getAllSubdirs(${subdir} ${_dirs})
    endforeach()
endfunction()

set(dirs ".")
getAllSubDirs(. ${dirs})
message("all dirs " ${dirs})

It seems like any recursive calls to getAllSubdirs doesn’t actually pass the updated _dirs to that function…

set(dirs ".")
getAllSubDirs(. ${dirs})

is equivalent to

getAllSubDirs(. ".")

I don’t think that is what you wanted to write.

That’s besides the point. Even when I tried to write a basic recursive function in CMake, it didn’t work as expected. It seems CMake doesn’t like recursively adding onto a list of some kind. For example, the following will execute forever…

function(simpleRecursive param)
    list(APPEND param "4;")
    set(${param} ${param} PARENT_SCOPE)
    if(NOT "${param}" STREQUAL "4;4;4;") # would end if STREQUAL "4;4;"
        simpleRecursive(${param})
    endif()
endfunction()

set(foo "4;")
simpleRecursive(${foo})
message(${foo})

So instead I made an iterative solution.

set(Project_Directories "")
get_property(stack DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY SUBDIRECTORIES)
while(stack)
    list(POP_BACK stack directory)
    list(APPEND Project_Directories ${directory})
    get_property(subdirs DIRECTORY ${directory} PROPERTY SUBDIRECTORIES)
    if(subdirs)
        list(APPEND stack ${subdirs})
    endif()
endwhile()

function(getTargets foundTargets)
    foreach(dir ${Project_Directories})
        get_property(target DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS)
        list(APPEND targets ${target})
    endforeach()
    set(${foundTargets} ${targets} PARENT_SCOPE)
endfunction()

getTargets(foo)

message("all targets" ${foo})

Calling a function opens a new scope, so it is possible to make a recursive function, but you need to call set(... PARENT_SCOPE) after doing the recursive call.

1 Like

I appreciate the continued feedback.

I still don’t understand your suggestion though. Can you fix the simpleRecursive function I made above to show what you mean? I can’t seem to get that simple example to work, despite what I set to PARENT_SCOPE.

I’m replying from my phone, so I didn’t check that it works, but I would write it like this:

function(simpleRecursive in_out_var)
    list(APPEND ${in_out_var} "4;")  # effectively appends to "foo" 
    if(NOT "${${in_out_var}}" STREQUAL "4;4;4;")
        simpleRecursive(${in_out_var})
    endif()
    set(${in_out_var} ${${in_out_var}} PARENT_SCOPE)  # effectively sets "foo" in the parent scope
endfunction()

set(foo "4;")
simpleRecursive(foo)  # pass the variable name, not its value 
message("${foo}")

You’re better than I if you can do that from your phone :slight_smile:

Unfortunately when I ran that it resulted in the same issue where the recursive call will never complete.

Out of curiosity, what do the double brackets mean, ${${in_out_var}}? If that wasn’t intentional, I tried your example without too, with no difference.

The recursion is broken because the condition doesn’t account for the actual behavior of list(APPEND). Running:

set(foo "4;")
list(APPEND foo "4;")
message("${foo}")

outputs

4;;4;

so the condition should be if(NOT "${${in_out_var}}" STREQUAL "4;;4;;4;").

Or we could rewrite the function to avoid this extra semicolon:

function(simpleRecursive in_out_var)
    list(APPEND ${in_out_var} "4")
    if(NOT "${${in_out_var}}" STREQUAL "4;4;4")
        simpleRecursive(${in_out_var})
    endif()
    set(${in_out_var} ${${in_out_var}} PARENT_SCOPE)
endfunction()

set(list_of_fours "4")
simpleRecursive(list_of_fours)
message("${list_of_fours}")

${${in_out_var}} means "the value of the value of in_out_var". Maybe an example can help:

function(print_variable var)
  message("${var}")  # this outputs "my_letter"
  message("${${var}}")  # this outputs "A"
endfunction()

set(my_letter "A")
print_variable(my_letter)

If you have more questions about the CMake syntax, don’t hesitate to create a new thread and to ping me on it!

1 Like

Thank you so much for the help! I think understanding ${${var}} was the missing key to my puzzle. Is that described somewhere in the CMake documentation?

For the curious, here is my updated recursive solution :slight_smile:

function(getAllSubdirs dir dirs)
    # get subdirectories for dir
    get_property(subdirs DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
    # iterate any found subdirectories
    foreach(subdir ${subdirs})
        # append each sub directory
        list(APPEND ${dirs} ${subdir})
        getAllSubdirs(${subdir} ${dirs})
    endforeach()
    set(${dirs} ${${dirs}} PARENT_SCOPE)
endfunction()

set(Project_Directories ${CMAKE_SOURCE_DIR})
getAllSubDirs(. Project_Directories)
message("all dirs " ${Project_Directories})

Yes, in https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#variable-references.

I’m using a different approach. It is more intrusive, but somewhat simple. Inside a cmake module, define a property to contain the list of targets. After using add_executable() or add_library(), use that macro to insert the target in the list. My use case was to generate an installation option for each target automatically. The list of targets can be used for something else I guess.

set_property(GLOBAL PROPERTY target_list)

# add_install_option must be a macro, if put in a function, the option does
# not exists right after being declared.
macro(add_install_option target)
    string(TOUPPER ${target} TGT)
    option(CONFIG_INSTALL_${TGT} "Install ${TGT}" ON)
    get_property(tmp GLOBAL PROPERTY target_list)
    list(APPEND tmp CONFIG_INSTALL_${TGT})
    set_property(GLOBAL PROPERTY target_list ${tmp})
endmacro()

I personally think this is something cmake should be able to tell us, instead of users trying to re-invent the wheel, as as proven in this discussion it seems hard. In case makefiles are generated, make does seems to know it since it allows tab completion on the targets, also something not possible when invoking via cmake --build --target.
I assume cmake when generating the build system has full view on it and this information is somewhere also present in the cmake cache ?
There are many scenarios to think of which can start by just having this information, in several build automations and CI scenarios.

Could this please be provided ?

1 Like

Late, but for anyone else finding this in search results, the OP’s original question can be accomplished with

function (_get_all_cmake_targets out_var current_dir)
    get_property(targets DIRECTORY ${current_dir} PROPERTY BUILDSYSTEM_TARGETS)
    get_property(subdirs DIRECTORY ${current_dir} PROPERTY SUBDIRECTORIES)

    foreach(subdir ${subdirs})
        _get_all_cmake_targets(subdir_targets ${subdir})
        list(APPEND targets ${subdir_targets})
    endforeach()

    set(${out_var} ${targets} PARENT_SCOPE)
endfunction()

# Run at end of top-level CMakeLists
_get_all_cmake_targets(all_targets ${CMAKE_CURRENT_LIST_DIR})
1 Like