Issue
For the following CMakeLists.txt
file
cmake_minimum_required(VERSION 3.15)
project(Testing)
include(CMakePackageConfigHelpers)
configure_package_config_file(FooConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake
INSTALL_DESTINATION lib/Goo/dmake
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake
DESTINATION lib/Foo/cmake )
and the FooConfig.cmake.in
file
@PACKAGE_INIT@
check_required_components(Foo)
It would finally install FooConfig.cmake
to lib/Foo/cmake
:
$ cmake ..
$ cmake --build .
$ cmake --install . --prefix ../install/
$ ls -R ../install/
../install/:
lib
../install/lib:
Foo
../install/lib/Foo:
cmake
../install/lib/Foo/cmake:
FooConfig.cmake
It seems that whatever value I set to the INSTALL_DESTINATION
option of configure_package_config_file
, it won't change FooConfig.cmake
's installation directory. But if I comment INSTALL_DESTINATION lib/Goo/dmake
, CMake Error prompts
No INSTALL_DESTINATION given to CONFIGURE_PACKAGE_CONFIG_FILE()
Then, what is the use of the INSTALL_DESINATION
option? What its value (specifically, the above setting lib/Goo/dmake
) actually affects?
The documentation:
The
<path>
given toINSTALL_DESTINATION
must be the destination where theFooConfig.cmake
file will be installed to.
I know the right way is INSTALL_DESTINATION lib/Foo/cmake
. But as I deliberately set it as lib/Goo/dmake
, the FooConfig.cmake
file still rests at the desired destination lib/Foo/cmake
. So, why this option is designed as a must?
Solution
The function configure_package_config_file only generates the file specified as the second argument. This function neither installs the generated file nor affects on its installation. So, its arguments can only affect on the content of the generated file.
If you look into the generated script FooConfig.cmake
, then you could find the line like
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
This is how the script calculates installation prefix based on the path to the FooConfig.cmake
file.
E.g. when you install the package with prefix /usr/local
and your FooConfig.cmake
script will be located in the directory /usr/local/lib/Foo/cmake
, then the quoted path will be expanded into /usr/local/lib/Foo/cmake/../../../
, which corresponds to /usr/local/
.
Number of components ../
in the script equals to the number of components in INSTALL_DESTINATION
parameter.
Why 'PACKAGE_PREFIX_DIR' is needed
Assume a package installs all public headers into directory include/
(relative to installation prefix), and config script needs to deliver that directory to the user via variable FOO_INCLUDE_DIR
.
The most direct way would be to write in the script following:
# FooConfig.cmake.in
...
set(FOO_INCLUDE_DIR @CMAKE_INSTALL_PREFIX@/include)
so configure_package_config_file
would substitute real install prefix instead of @CMAKE_INSTALL_PREFIX@
.
But embedding absolute path into the config file prevents the package to be relocatable. So, once installed, the package could be used only from the installation directory, and cannot be copied elsewhere.
For relocatable package a config file computes all installation paths relative to the path of the config file itself, because it is the only path known to the script when it is called by find_package
. It is like an executable can compute paths to the files located near to that executable.
If the script is installed into lib/Foo/cmake/FooConfig.cmake
, then relative path to the include directory would be ../../../include
, so one could use following assignment in that script:
# FooConfig.cmake.in
...
set(FOO_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../include)
So, when find_package(Foo)
will execute this script, it expands the variable CMAKE_CURRENT_LIST_DIR to the actual directory with the possibly relocated script.
Operating with relative paths requires much attention from the coder, so CMake allows to automate this task:
# CMakeLists.txt
...
# Path to the include directory in the installation tree
set(INCLUDE_DIR 'include')
configure_package_config_file(FooConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/FooConfig.cmake
INSTALL_DESTINATION lib/Foo/cmake
# Tell CMake to handle variable `INCLUDE_DIR` as a path
# under installation tree.
PATH_VARS INCLUDE_DIR
)
# FooConfig.cmake.in
@PACKAGE_INIT@
# Here we could use `@PACKAGE_INCLUDE_DIR@` as reference
# to variable 'INCLUDE_DIR' set in the CMakeLists.txt.
set_and_check(FOO_INCLUDE_DIR "@PACKAGE_INCLUDE_DIR@")
If you look into the generated file, you will find that @PACKAGE_INCLUDE_DIR@
is expanded into ${PACKAGE_PREFIX_DIR}/include
, which uses the variable PACKAGE_PREFIX_DIR
.
Answered By - Tsyvarev