Generator expressions in add_dependencies

Looking for potential info on how to handle the following case

I help maintain a piece of software, and im trying to bring our dependency building into the main cmake project. Essentially a meta build to build the project and all of its dependencies via the single cmake generate and build command.
The software is multi platform, covering Windows, Android, Linux, Apple (mac/ios/tvos), just to give a bit of info of all the bases i need to cover.

Ive been able to bring some dependencies to be handled (cmake based libs for first POC phase). To do this, we use externalproject_add to create a target to download/build/install the lib (potentially into an out of tree dir to reuse). We use externalproject_add as we often have to patch, or supply custom compilation options to each lib that are unique to the lib, and arent appropriate to the parent project.

In the basic case of a single config generator (eg Unix Makefiles), this works fine. The parent project is build (eg Debug build type), the libs are built in the same build type, everything links and works fine. You change to Release, repeat.

We are using custom Find modules to detect/build dependencies (allows us to utilise find_package() calls in the parent). We can then utilise system libs (linux/freebsd), build a lib (apply patch/build options appropriately) or find a prebuilt lib (our existing build system builds all the libs, and then a toolchain file is generated to point to them).

As the parent project is a large project (60+ dependencies), we are trying to use library cmake-config (or pkg-config) files to detect existing libs that meets the required criteria (eg version) to avoid rebuilding the lib. Some example code is as follows:

# Check for existing SPDLOG. If version >= SPDLOG-VERSION file version, dont build
 find_package(SPDLOG CONFIG QUIET)

 if(SPDLOG_VERSION VERSION_LESS "1.9.2"})
   BUILD_LIB()
 else()
   # Populate paths for find_package_handle_standard_args
   find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h)
   find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog)
   find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd)

 endif()

BUILD_LIB is a macro use to call externalproject_add with appropriate info (download/patch/build, etc)

The issue im coming up against is when multiconfig generators come into play (ie Xcode, Visual Studio).

The setup is, libs do not exist. Parent project is generated (cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/Toolchain.cmake -DCMAKE_INSTALL_PREFIX=/path/to/build/libs -G Xcode -DOPTION1=ON -DOPTION2=OFF Dev/parentproject), does find_package calls, Find module doesnt find the lib, it creates an externalproject_add target (spdlog), this target is added to a library as a dependency (add_library(Spdlog::Spdlog UNKNOWN IMPORTED)) where we set properties (location, interface_include_definitions, interface_compile_definitions, etc), which is then added to the parent using add_dependencies(parent Spdlog::Spdlog). The following code handles that last part in the Find module.

include(SelectLibraryConfigurations)
select_library_configurations(SPDLOG)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Spdlog
                                  REQUIRED_VARS SPDLOG_LIBRARY SPDLOG_INCLUDE_DIR
                                  VERSION_VAR SPDLOG_VERSION)

if(SPDLOG_FOUND)
  set(SPDLOG_LIBRARIES ${SPDLOG_LIBRARY})
  set(SPDLOG_INCLUDE_DIRS ${SPDLOG_INCLUDE_DIR})
  set(SPDLOG_DEFINITIONS -DSPDLOG_FMT_EXTERNAL
                         -DSPDLOG_DEBUG_ON
                         -DSPDLOG_NO_ATOMIC_LEVELS
                         -DSPDLOG_ENABLE_PATTERN_PADDING)
  if(WIN32)
    list(APPEND SPDLOG_DEFINITIONS -DSPDLOG_WCHAR_FILENAMES
                                   -DSPDLOG_WCHAR_TO_UTF8_SUPPORT)
  endif()

  if(NOT TARGET Spdlog::Spdlog)
    add_library(Spdlog::Spdlog UNKNOWN IMPORTED)
    if(SPDLOG_LIBRARY_RELEASE)
      set_target_properties(Spdlog::Spdlog PROPERTIES
                                           IMPORTED_CONFIGURATIONS RELEASE
                                           IMPORTED_LOCATION "${SPDLOG_LIBRARY_RELEASE}")
    endif()
    if(SPDLOG_LIBRARY_DEBUG)
      set_target_properties(Spdlog::Spdlog PROPERTIES
                                           IMPORTED_CONFIGURATIONS DEBUG
                                           IMPORTED_LOCATION "${SPDLOG_LIBRARY_DEBUG}")
    endif()
    set_target_properties(Spdlog::Spdlog PROPERTIES
                                         INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}"
                                         INTERFACE_COMPILE_DEFINITIONS "${SPDLOG_DEFINITIONS}")
  endif()
  if(TARGET spdlog)
    add_dependencies(Spdlog::Spdlog spdlog)
  endif()

endif()

With a multiconfig generator, the dev then builds the parent project (eg xcodebuild -config Debug -target parentproject)
This builds a Debug version of the lib (libspdlogd.a) into CMAKE_INSTALL_PREFIX, project completes, everything is fine.
If a user then deletes the cmake generated folder of the parent (or a new gitclone of the parent), we have the existing debug lib in the cmake_install_prefix. we want to reuse this lib if its appropriate.

A user then generates using the same command. The debug variant of the lib exists, find_library finds the debug, it doesnt find the release lib (doesnt exist). select_library_configuration handles this, and the debug lib is added and used regardless of the parent build type (eg xcodebuild -config Release -target parentproject).

This is all a bit long winded, but essentially what im looking for is a way to add the externalproject_add target as a dependency based on CONFIG generator expression.

How i picture us handling it would be using a property for each config type like below where we add

get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
  foreach(config ${CMAKE_CONFIGURATION_TYPES})
    set_property(GLOBAL PROPERTY INTERNAL_DEPS_PROP_${config})
  endforeach()
endif()

We would then detect when an externalproject_add call is required, and it its target to the property of the appropriate build type (eg INTERNAL_DEPS_PROP_Release would have spdlog target added, INTERNAL_DEPS_PROP_Debug would remain empty)
and then do something like the below to create a custom target based on build type

if(isMultiConfig)
  foreach(config ${CMAKE_CONFIGURATION_TYPES})
    get_property(INTERNAL_DEPS_${config} GLOBAL PROPERTY INTERNAL_DEPS_PROP_${config})
    if(INTERNAL_DEPS_${config})
      add_custom_target(dependencies_${config} COMMENT "Build missing dependencies for ${config} build type")
      add_dependencies(dependencies_${config} ${INTERNAL_DEPS_${config}})
    endif()
  endforeach()
endif()

and then (slimmed down to just be Debug/Release to “simplify”)

add_dependencies(parentproject $<IF:$<CONFIG:Debug>,${dependencies_Debug},${dependencies_Release}>)

dependencies_Debug would just have Spdlog::Spdlog (the debug spdlog lib exists so no externalproject_add is called to create the spdlog target), and dependencies_Release would have Spdlog::Spdlog;spdlog targets (which the parent would execute the Release type spdlog target created using externalproject_add).

Can anyone else think of a way to handle this that i havent been able to think of, or is a feature request to allow using a generator expression in add_dependencies suitable?

fetchcontent isnt an option as we need customised build options for the each lib

The question boils down to, how to add an externalproject_add target to a target via add_dependencies based on CONFIG in a multi generator project (VS/Xcode).

Sorry for the long winded post, but any suggestions would be greatly appreciated.

1 Like

for posterity: link to kitware issue