0

I have two projects sharing some code. Both have their own tools for generating almost the same boilerplate code, which are defined as a CMake target that runs everything when imported (or used as dependency). Let's assume that it is called generator_A in project A and generator_B in project B.

There are components that are shared "cloned" in both projects that use this boilerplate code, so for component_1 the CMakeLists.txt on each project would have something like:

# In project A
find_package(generator_A)
add_library(component_1 ...)
target_link_libraries(component_1 generator_A)
# In project B
find_package(generator_B)
add_library(component_1 ...)
target_link_libraries(component_1 generator_B)

Apart from this and other small differences regarding the generated code, component_1 would work in both projects. I would like to define some intermediate layer for the generators on each project, so that its usage is independent of the project and look like:

find_package(generator_unified)
add_library(component_1 ...)
target_link_libraries(component_1 generator_unified)

For reasons not under my control, I cannot change anything about the generators (e.g. names, how they work, generated code/products).

I have no idea what is the best way to do this. Some ideas that I've found searching docs and the internet:

  • Create a Findgenerator_unified.cmake file that defines a generator_unified::generator_unified from the products of generator_X in project_X (library, headers, other properties?). I am not really how this can be achieved, though.
    • Does generator_unified needs its own CMakeLists.txt file, or is it enough with the Find<>.cmake.
    • How can I make sure that the Find<>.cmake file is available for others?
  • Create some kind of alias that can be used project-wide. This doesn't look possible according to docs .

Is this a better way to achieve this? If 1st alternative is the correct,

dunadar
  • 1,745
  • 12
  • 30
  • *"which are defined as a CMake target that runs everything when imported (or used as dependency)"* could you elaborate? Generating code by defining/linking a target seems like a bad approach for most source generation. The way I'd try to make this work would be by defining a function in the find/configuration script that allows me to use invoke `add_custom_command` logic that is responsible for generation of any dependent files, e.g. `target_my_generator_generate_xyz(TARGET component_1 ... more options ...)` – fabian May 25 '22 at 17:06

1 Answers1

0

Something like this ought to work (your first alternative):

# Findgenerator_unified.cmake
cmake_minimum_required(VERSION 3.23)

if (${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
  set(quiet QUIET)
else ()
  set(quiet "")
endif ()

if (${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
  set(required REQUIRED)
else ()
  set(required "")
endif ()

set(known_backends generator_A generator_B)

set(GENERATOR_UNIFIED_BACKEND "generator_A"
    CACHE STRING "Which generator backend package to use.")

set_property(CACHE GENERATOR_UNIFIED_BACKEND
             PROPERTY STRINGS "${known_backends}")

foreach (backend IN LISTS known_backends)
  if (backend IN_LIST ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
    set(GENERATOR_UNIFIED_BACKEND "${backend}")
  endif ()
endforeach ()

if (GENERATOR_UNIFIED_BACKEND IN_LIST known_backends)
  find_package("${GENERATOR_UNIFIED_BACKEND}" ${quiet} ${required})
  set("${CMAKE_FIND_PACKAGE_NAME}_${GENERATOR_UNIFIED_BACKEND}_FOUND"
      "${${GENERATOR_UNIFIED_BACKEND}_FOUND}")
endif ()

unset(known_backends)
unset(quiet)
unset(required)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
  generator_unified
  HANDLE_COMPONENTS
  REQUIRED_VARS "${GENERATOR_UNIFIED_BACKEND}_FOUND"
  VERSION_VAR "${GENERATOR_UNIFIED_BACKEND}_VERSION"
)

if (generator_unified_FOUND AND NOT TARGET generator::unified)
  add_library(generator::unified ALIAS "${GENERATOR_UNIFIED_BACKEND}")
endif ()

This is mostly boilerplate, but the key is in the last few lines... the generator::unified target is set up as an alias to whichever backend was selected.

When a consuming project goes to install a target using this, they will need to generate code that forces the backend to be the same. The HANDLE_COMPONENTS flag naturally handles this because the component name matches the sub-package name. Users will write the following snippet in their project config files:

# proj-config.cmake.in

# ...

include(FindDependencyMacro)
find_dependency(generator_unified COMPONENTS @GENERATOR_UNIFIED_BACKEND@)

# ...
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86