Correctly exporting configuration

When looking closely at https://cmake.org/cmake/help/latest/guide/tutorial/Adding%20Export%20Configuration.html, I realize that I don’t seem to follow the logic of this tutorial. I’d appreciate a review of my procedure (which must come, at some time, from an example found on the net). NB in order to be clear, my procedure seems to work (tested on several machines, os and with various directory locations.
Also, CMAKE_INSTALL_PREFIX is set.

# Intall the target
install (TARGETS ${TARGET_NAME}
	EXPORT ${TARGET_NAME}Targets
	LIBRARY DESTINATION "${INSTALL_LIB_PATH}/${CONFIG}"
	ARCHIVE DESTINATION "${INSTALL_LIB_PATH}/${CONFIG}"
	RUNTIME DESTINATION "${INSTALL_BIN_PATH}/${CONFIG}"
	PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_PATH}"
	)

# Install the target export
install (EXPORT ${TARGET_NAME}Targets
	NAMESPACE ${TARGET_NAME}::
	DESTINATION "${COMPUTED_ABSOLUTE_PATH_TO_A_DEDICATED_DIRECTORY}"
)

# Install the ${TARGET_NAME}Config.cmake
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake"
	DESTINATION "${COMPUTED_ABSOLUTE_PATH_TO_A_DEDICATED_DIRECTORY}"
)

AFAIU, the first install tells to create a ${TARGET_NAME}Targets.cmake file in the build directory.
Paths are given relative to CMAKE_INSTALL_PREFIX.

The second one creates a namespace to wrap my target into it and copies the resulting ${TARGET_NAME}Targets.cmake into COMPUTED_ABSOLUTE_PATH_TO_A_DEDICATED_DIRECTORY
NB why don’t I need a install(EXPORT ${TARGET_NAME}Targets FILES ${TARGET_NAME}Targets.cmake DESTINATION "${COMPUTED_ABSOLUTE_PATH_TO_A_DEDICATED_DIRECTORY}"), as in the tutorial?

The last install is copying the remaining configuration files into the destination.
NB ${TARGET_NAME}Targets-Debug.cmake and ${TARGET_NAME}Targets-Release.cmake are also copied at the right place but I don’t know by what magic.

Eventually, the tutorial indicates the need for

target_include_directories(${TARGET_NAME}
                           INTERFACE
                            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                            $<INSTALL_INTERFACE:include>
                           )

but I don’t have it and I didn’t observe any issue (so far)

What may be the issues with my procedure and how should I fix them.

A guess is that the generated configuration files are full of absolute paths to the binaries and headers making them not compliant with https://cmake.org/cmake/help/latest/guide/tutorial/Packaging%20an%20Installer.html.
(yet I’d like, in the near future, the possibility to install my artefacts, without the need to configure and build them locally).

Regards
A.

[TEASER] I will make a separate question about the proper way to create configuration files. The procedure I found is also quite different from the tutorial and, in this case, IMHO, the tutorial is lacking information about how to declare and propagate dependencies through the configuration file.

you should not forget to write a ConfigVersion.cmake file

include(CMakePackageConfigHelpers)
include(GNUInstallDirs)

# find_package(<package>) call for consumers to find this project
set(package shared)

install(
    TARGETS shared example
    EXPORT sharedTargets
    RUNTIME #
    COMPONENT shared_Runtime
    LIBRARY #
    COMPONENT shared_Runtime
    NAMELINK_COMPONENT shared_Development
    ARCHIVE #
    COMPONENT shared_Development
    INCLUDES #
    DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    FILE_SET HEADERS
)

write_basic_package_version_file(
    "${package}ConfigVersion.cmake"
    COMPATIBILITY SameMajorVersion
)

# Allow package maintainers to freely override the path for the configs
set(
    shared_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package}"
    CACHE STRING "CMake package config location relative to the install prefix"
)
set_property(CACHE shared_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
mark_as_advanced(shared_INSTALL_CMAKEDIR)

install(
    FILES cmake/install-config.cmake
    DESTINATION "${shared_INSTALL_CMAKEDIR}"
    RENAME "${package}Config.cmake"
    COMPONENT shared_Development
)

install(
    FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
    DESTINATION "${shared_INSTALL_CMAKEDIR}"
    COMPONENT shared_Development
)

install(
    EXPORT sharedTargets
    NAMESPACE shared::
    DESTINATION "${shared_INSTALL_CMAKEDIR}"
    COMPONENT shared_Development
)

if(PROJECT_IS_TOP_LEVEL)
  include(CPack)
endif()

And you should test your exported config set i.e.:

cmake_minimum_required(VERSION 3.25...3.23)

project(sharedTests LANGUAGES CXX)

# ---- Dependencies ----

if(PROJECT_IS_TOP_LEVEL)
  find_package(shared 1.2.3 REQUIRED)
  enable_testing()
endif()

# ---- Tests ----

add_executable(shared_test source/shared_test.cpp)
target_link_libraries(shared_test PRIVATE shared::shared)
target_compile_features(shared_test PRIVATE cxx_std_17)

add_test(NAME shared_test COMMAND shared_test)

You’re obviously write and these functionalities are on my todolist for a (too) long time.

Yet do you see any issues with the procedure I’m using?
I’ll need also to look thouroughly into your snippets (especially the package manager related part, I think).

Best regards,
A.

Yes, you do not install the XxxConfigVersion.cmake file.
And you should use FileSets Headers.

I see that FILE_SET are CMake 3.23 and my procedure is from a much older version.
Thanks for the pointer. I have a bit of work to understand how it works.

FileSet Header are the modern way to prevent this.

Ah,

I’m afraid that CMake documentation is not clear enough on this point. Pulling the thread, I’m guessing that, before all that, I must declare my files with:

target_sources(<MyTarget> <PUBLIC|PRIVATE> FILE_SET Sources FILES file1.cpp file2.cpp ...)
target_sources(<MyTarget> <PUBLIC|INTERFACE> FILE_SET HEADERS FILES file1.h file2.h ...)

HEADERS seems to be a reserved keyword?

If I understand correctly, adding FILE_SET HEADERS with INCLUDES DESTINATION is telling to copy files from the set HEADERS into the given destination? It replaces PUBLIC_HEADER DESTINATION?

Btw, I’m still supporting pre-3.23 cmake version, what is the correct way to go in this case (doc about PUBLIC_HEADER makes me thing I’m wrong using it anyway)?

Just a clarification.

There are several configuration files that are exported. With given snippet:

  • <Target_Name>Targets.cmake: automatically generated
  • <Target_Name>Config.cmake: manually crafted and partialy edited by cmake (see also Correctly exporting configuration: second part)
  • <Target_Name>Targets-<Config>.cmake: automatically generated

NB1 <Target_Name>Config.cmake must finishes with a line including <Target_Name>Targets.cmake
NB2 In the example given in the second part of the question (linked above), I’m doing include("${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake"). How does it happen that ${CMAKE_CURRENT_LIST_DIR} is always resolving to the actual installation directory of the configuration file (maybe, from cmake documentation: When CMake starts processing commands in a source file it sets this variable to the directory where this file is located)

Then doing:

install (EXPORT ${TARGET_NAME}Targets
	NAMESPACE ${TARGET_NAME}::
	DESTINATION "${COMPUTED_ABSOLUTE_PATH_TO_A_DEDICATED_DIRECTORY}"
)

seems to have two effects:
declaring my target as <TargetName>::<TargetName>, a component named <TargetName> in a namespace TargetName: It’s at this step that I can have a different namespace name that my target one.

So it rises another question: when I want to link against some library, I’ve got in fact 3 independant names:

  • the name which is the root of the Config.cmake file
  • a namespace name
  • a component name

Am I correct in saying that find_package(<name1> COMPONENT <name2>)
will look for all components <name2> described in Config.cmake`, possibly finding several ones in different namespaces:

  • <namespace1>::<name2>
  • <namespace2>::<name2>
  • …`

?

And they I’ll have to pass the rightly namespace-qualified one to target_link_directory

@ben.boeckel
IMHO: the cmake tutorial step 11: Adding Export Configuration.html have to be reviewed!

1 Like