Friday, June 3, 2022

[SOLVED] correctly set the location of imported cmake targets for an installed package

Issue

I would like to be able to import targets from an installed library but when using:

install(TARGETS
        foobar
        EXPORT foobarLibTargets
        LIBRARY DESTINATION lib)

cmake generates a foobarLibTargets.cmake containing an absolute path:

set_target_properties(foobar PROPERTIES
        IMPORTED_LOCATION_NOCONFIG "/where/I/happened/to/build/libfoobar.so"
        IMPORTED_SONAME_NOCONFIG "libfoobar.so"
)

Such that a build using the imported target from the installation will fail as the path does not exist.

Q How can I get it to use the correct relative location instead?

This would be equivalent to:

set_target_properties(foobar PROPERTIES
                      IMPORTED_LOCATION_NOCONFIG "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so")

If I look at another project which does something similar but works it has:

set_target_properties(foobar PROPERTIES
         IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libfoobar.so"
         IMPORTED_SONAME_RELEASE "libfoobar.so"
)

Here are some example files that reproduce the issue:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.7)
project(FOOBAR VERSION 1.2.3)
set(VERSION 1.2.3)

set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
set(CMAKE_INSTALL_PREFIX "/opt/foobar" CACHE PATH "Install path prefix" FORCE)

add_library(foobar SHARED
  foobar.cpp
)

set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_NAME "foobar")

set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")

include(CPack)

# Indicate the content of the distribution pakcages
install(FILES
  ${CMAKE_SOURCE_DIR}/foobar.h
  DESTINATION include
)
install(TARGETS
  foobar
  EXPORT foobarLibTargets
  LIBRARY DESTINATION lib)

include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/foobar)
set(INCLUDE_INSTALL_DIR include)
set(LIBRARY_INSTALL_DIR lib)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
configure_package_config_file(foobarConfig.cmake.in
     "${CMAKE_BINARY_DIR}/foobarConfig.cmake"
    INSTALL_DESTINATION "${ConfigFileInstallDir}"
    PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR)
write_basic_package_version_file(
   "${CMAKE_BINARY_DIR}/foobarConfigVersion.cmake"
   VERSION "${VERSION}"
   COMPATIBILITY ExactVersion)
export(EXPORT foobarLibTargets
       FILE "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake")
install(EXPORT foobarLibTargets
        FILE foobarTargets.cmake
        DESTINATION lib/cmake)
install(FILES
   "${CMAKE_CURRENT_BINARY_DIR}/foobarConfig.cmake"
   "${CMAKE_CURRENT_BINARY_DIR}/foobarConfigVersion.cmake"
   "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake"
    DESTINATION "${ConfigFileInstallDir}")

foobarConfig.cmake.in:

set(FOOBAR_VERSION @VERSION@)

@PACKAGE_INIT@

set_and_check(FOOBAR_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set_and_check(FOOBAR_LIBRARY "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so")
set_and_check(FOOBAR_LIBRARY_DIR "@PACKAGE_LIBRARY_INSTALL_DIR@")
include("${CMAKE_CURRENT_LIST_DIR}/foobarLibTargets.cmake")

# workaround - correct absolute path in the above
# this shouldn't be necessary (hence this question)
#set_target_properties(foobar PROPERTIES
#  IMPORTED_LOCATION_NOCONFIG "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so"
#)

foobar.h:

void hello();

foobar.cpp:

#include <iostream>

void hello() {
   std::cerr << "hello world\n";
}

useFoo.cmake (a CMakeLists.txt for an example project using the installed library):

cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
set(VERSION 1.2.3)

find_package(foobar)
file(GENERATE OUTPUT foobar-gen CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")

message(STATUS "FOOBAR_LIBRARY_DIR=${FOOBAR_LIBRARY_DIR}")
message(STATUS "FOOBAR_INCLUDE_DIR=${FOOBAR_INCLUDE_DIR}")

build.sh (build and use the installation package):

#!/bin/sh

rm -rf target
mkdir target
cd target
cmake .. &&
make &&
cpack -G TGZ
if [ $? -ne 0 ]; then
   echo "doh!"
   exit 1
fi

cd ..
rm -rf install
mkdir install
cd install
tar -xvzf ../target/foobar-1.2.3.tar.gz
cp ../useFoo.cmake CMakeLists.txt
export CMAKE_PREFIX_PATH=`pwd`/opt/foobar/lib/cmake:`pwd`/opt/foobar/lib/cmake/foobar
cmake .
if [ $? -ne 0 ]; then
   echo "doh!"
   exit 1
fi
cat foobar-gen

The output of cat foobar-gen is:

<TARGET_FILE:foobar>=/where/I/happened/to/build/libfoobar.so

I would like it to be:

<TARGET_FILE:foobar>=/where/I/actually/installed/libfoobar.so

Which it becomes if I uncomment the workaround. Is there a way which avoids the workaround?

The related question - Strange issue with variables in a config-file cmake package - has similar code which both reproduces this issue and adds another one on top.


Solution

Only after instrumenting the source of cmake itself was I finally able to track this down.

The export and install commands are both capable of generating cmake files for targets. The export command e.g.:

export(EXPORT foobarLibTargets
       FILE "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake")

creates a Targets.cmake referencing the build tree.

The install command e.g.:

install(EXPORT foobarLibTargets
        FILE foobarTargets.cmake
        DESTINATION lib/cmake)

creates a Targets.cmake referencing the relocatable install location.

This is essentially what @J-Christophe meant by saying that two files were installed and the wrong one was picked up.

I had wrongly assumed that the install command was only responsible for installing files and the export command was only responsible for generating them.

The documentation makes sense now

export(EXPORT [NAMESPACE ] [FILE ])

The file created by this command is specific to the build tree and should never be installed. See the install(EXPORT) command to export targets from an installation tree.

The workaround I had previously is no longer necesary. For reference this was to explicitly set the correct location in the package's Config.cmake as in:

set(FOOBAR_VERSION @VERSION@)

@PACKAGE_INIT@

set_and_check(FOOBAR_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set_and_check(FOOBAR_LIBRARY "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so")
set_and_check(FOOBAR_LIBRARY_DIR "@PACKAGE_LIBRARY_INSTALL_DIR@")
include("${CMAKE_CURRENT_LIST_DIR}/foobarLibTargets.cmake")

# workaround - correct absolute path in the above
# this shouldn't be necessary!
set_target_properties(foobar PROPERTIES
                      IMPORTED_LOCATION_NOCONFIG  "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so"
)


Answered By - Bruce Adams
Answer Checked By - Mildred Charles (WPSolving Admin)