Using a header only library with FetchContents while build a static lib

Why must I install an header only lib which I only need at build time for my own library?

Claus-iMac:example clausklein$ cmake -B build -S . -G Ninja
-- The CXX compiler identification is AppleClang 12.0.0.12000032
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Downloading CPM.cmake to /Users/clausklein/cmake/example/build/cmake/CPM_0.28.2.cmake
-- Version: 7.1.3
-- Build type: 
-- CXX_STANDARD: 17
# ...
-- Performing Test FMT_HAS_VARIANT
-- Performing Test FMT_HAS_VARIANT - Success
-- Required features: cxx_variadic_templates
-- Configuring done
CMake Error: install(EXPORT "GreeterTargets" ...) includes target "Greeter" which requires target "fmt-header-only" that is not in any export set.
-- Generating done
CMake Generate step failed.  Build files cannot be regenerated correctly.
Claus-iMac:example clausklein$ 
cmake_minimum_required(VERSION 3.14...3.19)

# ---- Project ----

# Note: update this to your new project's name and version
project(
    Greeter
    VERSION 1.0
    LANGUAGES CXX
)

# ---- Project settings ----

if(NOT DEFINED CMAKE_CXX_STANDARD)
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    set(CMAKE_CXX_EXTENSIONS NO)
endif()

# ---- Add dependencies via CPM ----
# see https://github.com/TheLartians/CPM.cmake for more info

include(../CPM.cmake)

# CPMAddPackage(NAME PackageProject.cmake GITHUB_REPOSITORY TheLartians/PackageProject.cmake VERSION
# 1.4)
include(../PackageProject.cmake)
# PackageProject.cmake will be used to make our target installable

option(USE_FETCH_CONTENT "to show the problem is not caused by CPM" ON)
# XXX find_package(fmt 7.1)
if(NOT TARGET fmt::fmt-header-only)
    # NOTE: If fmt is not imported, we need to install it! CK
    # XXX option(FMT_INSTALL "" ON)
    option(FMT_INSTALL "" OFF)
    # to prevent CMake Error: install(EXPORT # "GreeterTargets" ...) includes target "Greeter" which
    # requires target "fmt" that is not in any export set.

    if(USE_FETCH_CONTENT)
        include(FetchContent)
        FetchContent_Declare(
            fmt
            GIT_REPOSITORY https://github.com/fmtlib/fmt.git
            GIT_TAG 7.1.3
        )
        FetchContent_MakeAvailable(fmt)
    else()
        CPMAddPackage(
            NAME fmt
            GIT_TAG 7.1.3
            GITHUB_REPOSITORY fmtlib/fmt
        )
    endif()
endif()

# ---- Create library ----

# Note: for header-only libraries change all PUBLIC flags to INTERFACE and create an interface
# target:
add_library(Greeter)
add_library(${PROJECT_NAME}::Greeter ALIAS Greeter)
target_compile_features(Greeter PUBLIC cxx_std_17)

target_sources(Greeter PRIVATE source/Greeter.cpp include/greeter/greeter.h)

# Link dependencies (if required)
target_link_libraries(Greeter PRIVATE fmt::fmt-header-only)

target_include_directories(
    Greeter PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
                   $<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)

# ---- Create an installable target ----
# this allows users to install and find the library via `find_package()`.

# the location where the project's version header will be placed should match the project's regular
# header paths
string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION)

packageProject(
    NAME ${PROJECT_NAME}
    VERSION ${PROJECT_VERSION}
    NAMESPACE ${PROJECT_NAME}
    BINARY_DIR ${PROJECT_BINARY_DIR}
    INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include
    INCLUDE_DESTINATION include/${PROJECT_NAME}
    VERSION_HEADER "${VERSION_HEADER_LOCATION}"
    DEPENDENCIES "" # optional list of dependencies
)

sipped from fmt config target:

# Create imported target fmt::fmt-header-only
add_library(fmt::fmt-header-only INTERFACE IMPORTED)

set_target_properties(fmt::fmt-header-only PROPERTIES
  INTERFACE_COMPILE_DEFINITIONS "FMT_HEADER_ONLY=1"
  INTERFACE_COMPILE_FEATURES "cxx_variadic_templates"
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)

snippet from installed config Target of my example:

# Create imported target Greeter
add_library(Greeter STATIC IMPORTED)

set_target_properties(Greeter PROPERTIES
  INTERFACE_COMPILE_FEATURES "cxx_std_17"
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/Greeter"
  INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:fmt::fmt-header-only>"
)

@craig.scott Is this a cmake bug or a usage error?

(tested with cmake version 3.14 and 3.19, Ninja and Makefile generators)

I went to reply earlier today with a statement that it was expected behavior because GreeterTargets is a static library. Normally, that means the things it links to has to be transitively applied to its consumers as a $<LINK_ONLY:...> requirement. That’s indeed what you’re seeing, but I’m wondering if that still is theoretically needed when the dependency (Greeter in this case) is only an interface library. I suspect the difficulty comes from the interface library could in turn pull in other libraries that are needed for linking. So I’m assuming CMake has to do what it is currently doing, but maybe someone else (@brad.king?) can provide a more concrete answer.

the difficulty comes from the interface library could in turn pull in other libraries that are needed for linking

Yes. See CMake Issue 15415.

Thanks, with this workaround it works fine

target_link_libraries(Greeter PRIVATE $<BUILD_INTERFACE:fmt::fmt-header-only>)

In “ from fmt config target” Which “fmt config target” ? if project is fmt, the cmake install(EXPORT generates file commonly named as fmtTargets.cmake which includes these 2 lines add_library for each exported target and set of property, but fmtConfig.cmake typically is different file generated from fmtConfig.cmake.in so which one are these lines from ?