4

I'd like to have one specific target be dependent on all the other added targets in my project. To say it in a different way - I'd like this target (say lint) to run after all the libraries and applications were built.

Is there a way in the top CMakeLists.txt file, where the project is specified, to get the list of all targets added by other CMakeLists.txt files in the directories added using add_subdirectory? Then I can use the add_dependencies to specify the order. There is a BUILDSYSTEM_TARGETS property, but it only works on a directory level.

If there is some other way to achieve this, please let me know. I use CMake 3.14.

Kevin
  • 16,549
  • 8
  • 60
  • 74
ilya1725
  • 4,496
  • 7
  • 43
  • 68
  • Have you checked the second answer for [that question](https://stackoverflow.com/questions/37434946/how-do-i-iterate-over-all-cmake-targets-programmatically) and both answers for [that question](https://stackoverflow.com/questions/48447627/do-something-for-all-targets)? – Tsyvarev Feb 13 '20 at 16:46
  • Thank you for the points. Yes, I did. There is mention of `BUILDSYSTEM_TARGETS` but it only works for the current directory and not the full tree. – ilya1725 Feb 13 '20 at 19:11

2 Answers2

8

You did not mention your CMake version, so I will assume 3.8 or better, for which this solution has been tested.

One possible solution is to iterate through all sub-directories in your project, and then apply BUILDSYSTEM_TARGETS to each of them. For the sake of simplicity and readability, I have split this up into three different macros.

First, we need a way of recursively obtaining all sub-directories in the project. For this, we can use file(GLOB_RECURSE ...) with LIST_DIRECTORIES set to ON:

#
# Get all directories below the specified root directory.
#   _result     : The variable in which to store the resulting directory list
#   _root       : The root directory, from which to start.
#
macro(get_directories _result _root)
    file(GLOB_RECURSE dirs RELATIVE ${_root} LIST_DIRECTORIES ON ${_root}/*)
    foreach(dir ${dirs})
        if(IS_DIRECTORY ${dir})
            list(APPEND ${_result} ${dir})
        endif()
    endforeach()
endmacro()

Secondly, we need a way to obtain all targets at a particular directory level. DIRECTORY takes an optional parameter, namely the directory you wish to query, which is key for this to work:

#
# Get all targets defined at the specified directory (level).
#   _result     : The variable in which to store the resulting list of targets.
#   _dir        : The directory to query for targets.
#
macro(get_targets_by_directory _result _dir)
    get_property(_target DIRECTORY ${_dir} PROPERTY BUILDSYSTEM_TARGETS)
    set(_result ${_target})
endmacro()

Thirdly, we need another macro to tie all this together:

#
# Get all targets defined below the specified root directory.
#   _result     : The variable in which to store the resulting list of targets.
#   _root_dir   : The root project root directory
#
macro(get_all_targets _result _root_dir)
    get_directories(_all_directories ${_root_dir})
    foreach(_dir ${_all_directories})
        get_targets_by_directory(_target ${_dir})
        if(_target)
            list(APPEND ${_result} ${_target})
        endif()
    endforeach()
endmacro()

Finally, here is how you can use it:

get_all_targets(ALL_TARGETS ${CMAKE_CURRENT_LIST_DIR})

ALL_TARGETS should now be a list holding the names of every target created below the caller's directory level. Note that it does not include any targets created in the current CMakeLists.txt. For that, you can make an extra call to get_targets_by_directory(ALL_TARGETS ${CMAKE_CURRENT_LIST_DIR}).

thomas_f
  • 1,854
  • 19
  • 27
  • awesome. Why did you have to check `if(IS_DIRECTORY ${dir})`? Isn't the `file()` only returns directories? – ilya1725 Feb 13 '20 at 23:56
  • @ilya1725 No, it lists files too. – thomas_f Feb 14 '20 at 05:42
  • @ilya1725 `LIST_DIRECTORIES ON` merely instructs `file()` to also include directories, which is turned off by default when using `GLOB_RECURSE`. – thomas_f Feb 14 '20 at 09:08
  • When I try this code, no (relative) directory path is validated as a directory by `IS_DIRECTORY`. Thus, the macro `get_directories` do not really work. Maybe this is because I run CMake v3.18.2? – Smartskaft2 Jun 22 '21 at 06:58
  • @Smartskaft2 Just tried this using CMake 3.19, and it seems to work as intented. Maybe try Ilya's solution (the other answer)? – thomas_f Jun 22 '21 at 07:20
  • @thomas_f Thank you for suggesting that, his answer worked fine for me. – Smartskaft2 Jun 22 '21 at 08:17
8

For the future reference, I've ended up writing my own function instead of a macro. But the concept is the same as the accepted answer by @thomas_f.

Here is the code:

# Collect all currently added targets in all subdirectories
#
# Parameters:
# - _result the list containing all found targets
# - _dir root directory to start looking from
function(get_all_targets _result _dir)
    get_property(_subdirs DIRECTORY "${_dir}" PROPERTY SUBDIRECTORIES)
    foreach(_subdir IN LISTS _subdirs)
        get_all_targets(${_result} "${_subdir}")
    endforeach()

    get_directory_property(_sub_targets DIRECTORY "${_dir}" BUILDSYSTEM_TARGETS)
    set(${_result} ${${_result}} ${_sub_targets} PARENT_SCOPE)
endfunction()
ilya1725
  • 4,496
  • 7
  • 43
  • 68