0

Consider a library frob that can be built as shared and static library in one go. That's usually achieved with two libraries that only differ in STATIC / SHARED (c.f. setup details below). For convenience we add a third interface library (automatic) solely for selecting the right library version depending on BUILD_SHARED_LIBS.

That works very well when building the project itself. But the library should also be consumable from an install. In that case, the auto-detection for automatic does not work any more. This is due to the fact that the criterion is evaluated in the installed project, rather than the consuming one (Frob::automatic has a fixed dependency on either static or shared in the installed FrobConfig.cmake).

Question: How to install Frob::automatic s.t. it depends on either Frob::static or Frob::shared depending on BUILD_SHARED_LIBS (of a consuming project)? In other words, how to install a dynamic dependency (e.g. depending on BUILD_SHARED_LIBS)?


Setup for Frob

Let's start with the very basic library setup for frob:

project(frob CXX)

# User configuration
option(FROB_BUILD_STATIC "Build statically linked library version." ON)
option(FROB_BUILD_SHARED "Build shared, dynamically linked library version." ON)

if (FROB_BUILD_STATIC)
    add_library(frob-static STATIC ...)
    set_target_properties(frob-static PROPERTIES EXPORT_NAME static)
    add_library(Frob::static ALIAS frob-static)
endif()
if (FROB_BUILD_SHARED)
    add_library(frob-shared SHARED ...)
    set_target_properties(frob-shared PROPERTIES  EXPORT_NAME shared)
    add_library(Frob::shared ALIAS frob-shared)
endif()


# For convenience, we provide an interface library to automagically select the right version 
# (depending on build setup and `BUILD_SHARED_LIBS`)
add_library(frob-automatic INTERFACE)
if (FROB_BUILD_SHARED AND (BUILD_SHARED_LIBS OR NOT FROB_BUILD_STATIC))
    target_link_libraries(frob-automatic INTERFACE frob-shared)
else ()
    target_link_libraries(frob-automatic INTERFACE frob-static)
endif ()
add_library(Frob::automatic ALIAS frob-automatic)
set_target_properties(frob-automatic PROPERTIES EXPORT_NAME automatic)


# Furthermore `frob` should be installable:
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
    include(GNUInstallDirs)
    # [...] meta data
    install(
        TARGETS frob-shared frob-static frob-automatic
        EXPORT frob
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    )
    install(
        EXPORT frob
        FILE FrobConfig.cmake
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Frob
        NAMESPACE Frob::
    )
    include(CPack)
endif()

Setup of a consumer

Consider the consuming project consume

project(consume CXX)

set(BUILD_SHARED_LIBS OFF)

add_executable(auto Automatic.cpp)
target_link_libraries(auto Frob::automatic)

The above question is how to install frob s.t. the configuration of BUILD_SHARED_LIBS in the consume-ing project controls whether auto is linked against Frob::static or Frob::shared.

m8mble
  • 1,513
  • 1
  • 22
  • 30
  • 2
    Basically you don't install this target at all, but instead provide a configuration script that creates an alias lib Frob::automatic depending on the configuration. – fabian Apr 25 '21 at 15:58
  • Sounds good! How do you install any cmake-code (in frob) that a consuming project executes upon configuration? – m8mble Apr 25 '21 at 19:49
  • 1
    I'd install the configuration scripts for the static/dynamic lib to a dir where cmake does not automatically pick them up as the configuration script and add a "hand crafted" config script containing the logic in the dir where `find_package`. The cmake files provided by a boost installation does check multiple configurations for a suitable one btw; perhaps it may be worth checking out those (Note though that as of version 1.74 there was no cmake project doing the same as the boost build tool (b2)). – fabian Apr 25 '21 at 20:01

1 Answers1

0

The trick is to not install automatic, but provide it via an extra layer of indirection. Here are the updated project details of frob:

# [...] as above

if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
    include(GNUInstallDirs)
    # [...] meta data
    install(
        TARGETS frob-shared frob-static # no frob-automatic any more
        EXPORT frob
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    )
    install(
        EXPORT frob
        FILE FrobConfigGenerated.cmake # new Generated suffix
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Frob
        NAMESPACE Frob::
    )
    # Here's the new layer of indirection.
    install(
        FILES FrobConfig.cmake
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Frob
    )
    include(CPack)
endif()

where the new FrobConfig.cmake contains

# Import central (generated) configuration details.
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(${_DIR}/FrobConfigGenerated.cmake)

# Introduce automatic aliases
if (BUILD_SHARED_LIBS)
    add_library(Frob::automatic ALIAS Frob::shared)
else ()
    add_library(Frob::automatic ALIAS Frob::static)
endif ()

When a consuming project (i.e. after find_package(Frob)) links against Frob::automatic that references Frob::shared / Frob::static depending on BUILD_SHARED_LIBS.

Substantial credit to fabian's comment.

m8mble
  • 1,513
  • 1
  • 22
  • 30