CMake imported shared library links with relative path, breaks install - cmake

I have a vendor library which needs to be included with my project's installer, I'm installing it with INSTALL(FILES ...). The library is stored along with the source, the problem is that the linked path (shown by readelf) is relative and that prefix doesn't get removed during installation, so instead of the dynamic linker searching for hhlib.so its looking for ../hhlib-linux-64bit/hhlib.so.
How can I get a working installed binary? Can I get the install step or CPack to remove this relative path to the library?
I've reduced the problem down to a simple example:
.
├── build
├── CMakeLists.txt
├── hhlib-linux-64bit
│   ├── hhlib.h
│   └── hhlib.so
└── use_hydroharp.c
cmake_minimum_required (VERSION 3.19)
# These sets have no effect on the issue
# use, i.e. don't skip the full RPATH for the build tree
#SET(CMAKE_SKIP_BUILD_RPATH FALSE)
# when building, don't use the install RPATH already
# (but later on when installing)
#SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
# the RPATH to be used when installing
#SET(CMAKE_INSTALL_RPATH "")
# don't add the automatically determined parts of the RPATH
# which point to directories outside the build tree to the install RPATH
#SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
enable_language(C)
# Find the header files
find_path(HydroHarp_INCLUDEDIR
NAMES hhlib.h
PATHS "${CMAKE_CURRENT_SOURCE_DIR}/hhlib-linux-64bit"
)
# Find the shared library
find_library(HydroHarp_LIBRARY
NAMES hhlib.so
HINTS "${CMAKE_CURRENT_SOURCE_DIR}/hhlib-linux-64bit"
)
if(HydroHarp_INCLUDEDIR AND HydroHarp_LIBRARY)
message("Found HyroHarp library")
# Get the containing folder for the library
get_filename_component(HydroHarp_LIBDIR ${HydroHarp_LIBRARY} DIRECTORY)
# Import the library
add_library(HydroHarp SHARED IMPORTED)
set_target_properties(HydroHarp PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${HydroHarp_INCLUDEDIR}
IMPORTED_LOCATION ${HydroHarp_LIBRARY}
)
# copy the library when installing
install(FILES ${HydroHarp_LIBRARY} TYPE LIB)
else()
message(WARNING "Failed to find HydroHarp")
endif()
project(extlink)
add_executable(${PROJECT_NAME} use_hydroharp.c)
target_link_libraries(${PROJECT_NAME} PRIVATE HydroHarp)
# install the library
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)
set(CPACK_GENERATOR DEB CACHE INTERNAL "")
SET(CPACK_PACKAGE_CONTACT "asdaksd#kjhk.com")
SET(CPACK_PACKAGE_VENDOR "asdfasd")
include(CPack)
Built with:
build$ cmake .. && make package
The program references the shared library with a relative path (which is fine for the build tree but not needed due to the RUNPATH):
build$ readelf -d extlink
Dynamic section at offset 0x2da8 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [../hhlib-linux-64bit/hhlib.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [/code/hhlib-linux-64bit:]
0x000000000000000c (INIT) 0x1000
...
but after make install it's still there and the RUNPATH has been removed:
build$ readelf -d /usr/bin/extlink
Dynamic section at offset 0x2da8 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [../hhlib-linux-64bit/hhlib.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x1000
I'm building on Ubuntu 20.04 with CMake 3.16.3 but also tested with the latest CMake version 3.19.4
At first I thought this issue provided a solution, but no combination of those RPATH variables fixes the problem.

After a nice, long scream into my favourite screaming pillow I had another think:
Adding the property IMPORTED_NO_SONAME removes the offending path but the linker fails to find it - even when the path is explicitly included with LINK_DIRECTORIES, why? Because the vendor library doesn't follow the naming convention!
Renaming the library to libhh.so and adding set_target_properties(HydroHarp PROPERTIES IMPORTED_NO_SONAME TRUE) fixes the issue. Don't ask me why.
The full working CMakeLists.txt:
cmake_minimum_required (VERSION 3.19)
enable_language(C)
# Find the header files
find_path(HydroHarp_INCLUDEDIR
NAMES hhlib.h
PATHS "${CMAKE_CURRENT_SOURCE_DIR}/hhlib-linux-64bit"
)
# Find the shared library
find_library(HydroHarp_LIBRARY
NAMES hh # <-- Note removed .so so standard extensions are searched
HINTS "${CMAKE_CURRENT_SOURCE_DIR}/hhlib-linux-64bit"
)
if(HydroHarp_INCLUDEDIR AND HydroHarp_LIBRARY)
message("Found HyroHarp library")
# Import the library
add_library(HydroHarp SHARED IMPORTED)
set_target_properties(HydroHarp PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${HydroHarp_INCLUDEDIR}
IMPORTED_NO_SONAME TRUE # <-- This lib wasn't built with an SONAME
IMPORTED_LOCATION ${HydroHarp_LIBRARY}
)
# copy the library when installing
install(FILES ${HydroHarp_LIBRARY} TYPE LIB)
else()
message(WARNING "Failed to find HydroHarp")
endif()
project(extlink)
add_executable(${PROJECT_NAME} use_hydroharp.c)
target_link_libraries(${PROJECT_NAME} PRIVATE HydroHarp)
# install the library
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)
set(CPACK_GENERATOR DEB CACHE INTERNAL "")
SET(CPACK_PACKAGE_CONTACT "asdaksd#adssd.com")
SET(CPACK_PACKAGE_VENDOR "asdfasd")
include(CPack)

Related

What's the proper way to package C++ program with CMake and CPack for program installation and standalone execution

I have written a simple c++ program intended to be built and packaged with CMake. The simple program depends on a third-party (OpenCV) library which can be found by find_package(OpenCV REQUIRED) in my CMake project. I want to build and package my simple program including the runtime library (Prerequisite Shared Libraries from OpenCV) so that I can produce a Windows installer file that can be installed and run on other Windows machines.
My project structure is as follows:
HAND-POSE
│ CMakeLists.txt
│ handpose.cpp
│ README.md
├───cmake
│ FixBundle.cmake.in
├───data
│ video.mp4
└───models
pose_deploy.prototxt
pose_iter_102000.caffemodel
I spent some time and got some documentation (here & here) from the CMake website, and tried to adapt my project in the following ways:
The CMakeLists.txt file is as follows:
cmake_minimum_required(VERSION 3.0.0)
project(HandPose VERSION 0.1.0)
set(CMAKE_PREFIX_PATH C:\\OpenCV-4.6.0)
include(CTest)
enable_testing()
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
ADD_EXECUTABLE(handpose handpose.cpp)
TARGET_LINK_LIBRARIES(handpose ${OpenCV_LIBS})
include(InstallRequiredSystemLibraries)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/FixBundle.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake
#ONLY
)
install(TARGETS handpose
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
The FixBundle.cmake.in file is as follows:
include(BundleUtilities)
# Set bundle to the full path name of the executable already
# existing in the install tree:
set(bundle "Release/handpose#CMAKE_EXECUTABLE_SUFFIX#")
# Set other_libs to a list of full path names to additional
# libraries that cannot be reached by dependency analysis.
# (Dynamically loaded PlugIns, for example.)
# set(other_libs "")
# Set dirs to a list of directories where prerequisite libraries
# may be found:
set(dirs
"#CMAKE_RUNTIME_OUTPUT_DIRECTORY#"
"#CMAKE_LIBRARY_OUTPUT_DIRECTORY#"
"C:\\OpenCV-4.6.0\\x64\\vc16\\bin"
)
fixup_bundle("${bundle}" \"\" "${dirs}")

cmake linker error with library installed in custom path

I have compiled and installed with CMake the library SDL_bgi to a custom prefix /custom/prefix/. This library uses SDL2.
Now I want to use it in another project with the structure below but I get a linker error when I compile with make:
/usr/bin/c++ CMakeFiles/test.dir/test.cpp.o -o test -Wl,-rpath,/custom/prefix/lib: /custom/prefix/lib/libSDL_bgi.so
/usr/bin/ld: /custom/prefix/lib/libSDL_bgi.so: undefined reference to `SDL_DestroyWindow'
/usr/bin/ld: /custom/prefix/lib/libSDL_bgi.so: undefined reference to `SDL_CreateRenderer'
I have also written the file cmake/modules/FindSDL_bgi.cmake so that may be wrong as well.
If I compile with the following command I can compile correctly:
g++ test.cpp -I . -lSDL_bgi -lSDL2 -I /custom/prefix/include/ -L /custom/prefix/lib/
What am I doing wrongly?
Project structure:
cmake/modules/FindSDL_bgi.cmake
src/test/CMakeLists.txt
src/test/test.cpp
CMakeLists.txt
Libraries:
/usr/lib/libSDL.so
/usr/include/SDL.h
/custom/prefix/lib/libSDL_bgi.so
/custom/prefix/include/graphics.h
/custom/prefix/include/SDL2/libSDL_bgi.h
cmake/modules/FindSDL_bgi.cmake:
# - Try to find LibXml2
# Once done this will define
# SDL_BGI_FOUND - System has LibXml2
# SDL_BGI_INCLUDE_DIRS - The LibXml2 include directories
# SDL_BGI_LIBRARIES - The libraries needed to use LibXml2
# Hardcoded for now
set(SDL_BGI_PATH
/custom/prefix/
)
set(SDL_BGI_SEARCH_PATHS
/usr
/usr/local
/opt
${SDL_BGI_PATH}
)
find_path(SDL_BGI_INCLUDE_DIR graphics.h
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES include
PATHS ${SDL2_SEARCH_PATHS}
)
find_library(SDL_BGI_LIBRARY
NAMES SDL_bgi
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES lib64 lib
PATHS ${SDL2_SEARCH_PATHS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SDL_bgi REQUIRED_VARS SDL_BGI_LIBRARY SDL_BGI_INCLUDE_DIR)
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(programmi_kennedy)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules")
set(COMPAT_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/include/
)
find_package(SDL_bgi REQUIRED)
add_subdirectory(src/test)
src/CMakeLists.txt:
add_executable(test test.cpp)
target_include_directories(test PUBLIC ${SDL_BGI_INCLUDE_DIR})
target_link_libraries(test PRIVATE ${SDL_BGI_LIBRARY})
install(TARGETS test DESTINATION bin)
/custom/prefix/include/graphics.h:
#include <SDL2/SDL_bgi.h>
What I was missing is to link to SDL2 with find_package(SDL2 REQUIRED) and link to SDL2::SDL2. (I did try to link to ${SDL2_LIBRARIES} but the syntax is different now). Thanks to #KamilCuk to point me to the right direction.
EDIT:
I changed the FindBGI_sdl.cmake module in order to search for the dependencies (SDL2) and link against them using the INTERFACE keyword. In this way the target test can link only against SDL_bgi and have the dependencies resolved automatically.
src/CMakeLists.txt:
add_executable(test test.cpp)
target_link_libraries(test PRIVATE SDL_bgi::SDL_bgi)
install(TARGETS test DESTINATION bin)
cmake/modules/FindSDL_bgi.cmake:
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindSDL_bgi
-------
Finds the SDL_bgi library.
Imported Targets
^^^^^^^^^^^^^^^^
This module provides the following imported targets, if found:
``SDL_bgi::SDL_bgi``
The SDL_bgi library
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``SDL_bgi_FOUND``
True if the system has the SDL_bgi library.
``SDL_bgi_VERSION``
The version of the SDL_bgi library which was found.
``SDL_bgi_INCLUDE_DIRS``
Include directories needed to use SDL_bgi.
``SDL_bgi_LIBRARIES``
Libraries needed to link to SDL_bgi.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set:
``SDL_bgi_INCLUDE_DIR``
The directory containing ``foo.h``.
``SDL_bgi_LIBRARY``
The path to the SDL_bgi library.
#]=======================================================================]
find_package(SDL2 REQUIRED)
find_package(PkgConfig)
pkg_check_modules(PC_SDL_bgi QUIET SDL_bgi)
find_path(SDL_bgi_INCLUDE_DIR
NAMES graphics.h
PATHS ${PC_SDL_bgi_INCLUDE_DIRS}
)
find_library(SDL_bgi_LIBRARY
NAMES SDL_bgi
PATHS ${PC_SDL_bgi_LIBRARY_DIRS}
)
set(SDL_bgi_VERSION ${PC_SDL_bgi_VERSION})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SDL_bgi
FOUND_VAR SDL_bgi_FOUND
REQUIRED_VARS
SDL_bgi_LIBRARY
SDL_bgi_INCLUDE_DIR
VERSION_VAR SDL_bgi_VERSION
)
if(SDL_bgi_FOUND AND NOT TARGET SDL_bgi::SDL_bgi)
add_library(SDL_bgi::SDL_bgi UNKNOWN IMPORTED)
set_target_properties(SDL_bgi::SDL_bgi PROPERTIES
IMPORTED_LOCATION "${SDL_bgi_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PC_SDL_bgi_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${SDL_bgi_INCLUDE_DIR}"
)
target_link_libraries(SDL_bgi::SDL_bgi INTERFACE SDL2::SDL2)
endif()
mark_as_advanced(
SDL_bgi_INCLUDE_DIR
SDL_bgi_LIBRARY
SDL2_DIR
)
Useful references:
https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
https://cmake.org/cmake/help/v3.17/manual/cmake-developer.7.html

Strange issue with variables in a config-file cmake package

We can use a cmake config file to import targets.
For example given machinary including foobarConfig.cmake.in
set(FOOBAR_VERSION #VERSION#)
#PACKAGE_INIT#
set_and_check(FOOBAR_INCLUDE_DIR "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
set_and_check(FOOBAR_STATIC_LIBRARY #PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.a")
include("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
message(STATUS "foobar version: ${FOOBAR_VERSION}")
message(STATUS "foobar include location: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location: ${FOOBAR_LIBRARY_DIR}")
for an exported target foobar
We can do:
find_package(foobar)
add_executable(usesfoo
usesfoo.cpp)
target_link_libraries(usesfoo
${FOOBAR_LIBRARY})
target_include_directories(usesfoo PUBLIC
${FOOBAR_INCLUDE_DIR})
and it normally just works.
However, I have a strage case where variables set in the Config.cmake are not available after find_package.
For example given:
find_package(foobar REQUIRED)
if (foobar_FOUND)
message(STATUS "found foobar")
endif()
message(STATUS "foobar include location2: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location2: ${FOOBAR_LIBRARY_DIR}")
The output is:
foobar include location: /test-import/opt/foobar/include
foobar library location: /test-import/opt/foobar/lib
found foobar
foobar include location2:
foobar library location2:
What could be going on here?
How can I:
Find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
I got very confused trying to debug this and started to question how Config packages are supposed to work.
Should I be using properties of imported targets instead of variables?
What scope does find_package run in? I thought it was like an include() rather than an add_subdirectory() - which introduces its own scope.
How can these variables become unset?
What is find_package doing under the hood?
See also correctly set the location of imported cmake targets for an installed package.
That question contains code to reproduce that problem which is similar to the code for this problem.
Complete set of files to reproduce the problem:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)
set(VERSION 1.3.3)
project(FoobarLib VERSION "${VERSION}" LANGUAGES CXX)
SET(CMAKE_INSTALL_PREFIX "/opt/foo")
set(INSTALL_LIB_DIR lib)
add_library(foobar SHARED
foobar.cpp
)
# Create the distribution package(s)
set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_NAME "foobar")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
set(LIBRARY_INSTALL_DIR lib)
set(INCLUDE_INSTALL_DIR include)
INSTALL(TARGETS foobar
EXPORT FoobarLibTargets
LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${LIBRARY_INSTALL_DIR}
INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR})
include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/FoobarLib)
set(INCLUDE_INSTALL_DIR include CACHE PATH "install path for include files")
set(LIBRARY_INSTALL_DIR lib CACHE PATH "install path for libraries")
configure_package_config_file(FoobarLibConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
INSTALL_DESTINATION "${ConfigFileInstallDir}"
PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
VERSION "${VERSION}"
COMPATIBILITY SameMajorVersion)
EXPORT(EXPORT FoobarLibTargets
FILE FoobarLibTargets.cmake)
INSTALL(FILES
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibTargets.cmake"
DESTINATION "${ConfigFileInstallDir}")
include(CPack)
FoobarLibConfig.cmake.in:
set(FoobarLib_VERSION #VERSION#)
#PACKAGE_INIT#
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
check_required_components(FoobarLib)
run.sh:
#!/bin/sh
SRC=`pwd`
mkdir -p ./target/debug && \
cd ./target/debug &&
cmake -DCMAKE_BUILD_TYPE=Debug ../../ &&
make &&
cpack -G TGZ
cd ../..
rm -rf foo
mkdir foo
TGZ=`pwd`/target/debug/foobar-1.3.3.tar.gz
cd foo
tar -xvzf $TGZ
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
export CMAKE_PREFIX_PATH=`pwd`/opt/foo/lib/cmake:`pwd`/opt/foo/lib/cmake/
cmake . && make VERBOSE=1
echo pwd=`pwd`
# critical - check the location of the target is relative to the installation
grep $WSDIR/opt/foo/lib/libfoobar.so foobar-loc
if [ $? -ne 0 ]; then
echo "FAIL: location of imported target 'foobar' is incorect" >&2
cat foobar-loc >&2
exit 1
fi
Here is the generated Config.cmake as requested by #havogt I don't think it helps as it is the standard generated code:
# CMake configuration file for the FoobarLib package
# Use with the find_package command in config-mode to find information about
# the FoobarLib package.
#
set(FoobarLib_VERSION 1.3.3)
####### Expanded from #PACKAGE_INIT# by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was FoobarLibConfig.cmake.in ########
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
####################################################################################
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "${PACKAGE_PREFIX_DIR}/lib")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so")
check_required_components(FoobarLib)
'package'_FOUND is set by the implementation of find_package() not by the Config.cmake that it loads. Adding check_required_components() is good practice for other reasons (picking up that someone thinks the package is componentised when it isn't) but is not relevant to this issue.
Oops. This is embarrassing. I'd moved the generation code into a shell script and forgot to escape the variables!
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
The question is still useful for providing source for the related question though.
To answer my own questions:
How can I find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
https://en.wikipedia.org/wiki/Rubber_duck_debugging
Reduce the problem to a minimum reproducible example (preferably before posting on stack overflow)
Avoid (or at least take extra care) generating code from shell scripts
Reduce stress and get more sleep
check_required_components(Foobar) should be called at the end in the case. The docs.
check_required_components() should be called at the end
of the FooConfig.cmake file. This macro checks whether all requested,
non-optional components have been found, and if this is not the case,
sets the Foo_FOUND variable to FALSE, so that the package is
considered to be not found. It does that by testing the
Foo__FOUND variables for all requested required components.
This macro should be called even if the package doesn’t provide any
components to make sure users are not specifying components
erroneously. When using the NO_CHECK_REQUIRED_COMPONENTS_MACRO option,
this macro is not generated into the FooConfig.cmake file.

Modern CMake transitive Dependency not found

I am currently working on implementing a ROS library into our company software stack. Because the library is based on ROS and thus uses catkin I am rewriting the library to use cmake exclusively and try to apply the modern CMake approach. The library is structured as follows:
.
|-- CMakeLists.txt
|-- LICENSE
|-- README.md
|-- grid_map_core
| |-- CHANGELOG.rst
| |-- CMakeLists.txt
| |-- cmake
| | `-- grid_map_core-extras.cmake
| |-- grid_map_coreConfig.cmake
| |-- include
| | `-- grid_map_core
| | `-- iterators
| |-- src
| | `-- iterators
| `-- test
If I install the library and try to add the library in a simple test_project to the target I get an error displaying the Eigen3 dependency cannot be found:
CMake Error at CMakeLists.txt:6 (find_package):
Found package configuration file:
/usr/local/lib/cmake/grid_map_core/grid_map_coreConfig.cmake
but it set grid_map_core_FOUND to FALSE so package "grid_map_core" is
considered to be NOT FOUND. Reason given by package:
grid_map_core could not be found because dependency Eigen3 could not be
found.
Unfortunately the Eigen version I have to use does not provide the Eigen3Config.cmake option and I am forced to use the cmake provided FindEigen3.cmake alternative.
(I suppose compiling a newer Eigen3 version manually would be a valid alternative, nevertheless I try to completely understand the modern cmake approach which looks very promising for exactly avoiding such issues)
From all the resources online I am not quite sure how the transitive dependency is handled in this case.
To my understanding the grid_map_coreConfig.cmake should forward the imported Eigen3 dependency.
In the grid_map_core CMakeLists the eigen is found by the command find_package(Eigen3 3.2 REQUIRED) and the find_dependency macro just wraps this exact same command.
Resources
The main CmakeLists.txt looks as follows:
# Set cmake version
cmake_minimum_required(VERSION 3.0.2)
# Set project name
project(grid_map)
# Must use GNUInstallDirs to install libraries into correct
# locations on all platforms.
include(GNUInstallDirs)
add_compile_options(-std=c++11)
# Add subdirectories
add_subdirectory(grid_map_core)
The grid_map_core CMakeLists as follows:
# Set cmake version
cmake_minimum_required(VERSION 3.0.2)
# Set project name
project(grid_map_core)
add_compile_options(-std=c++11)
# import Eigen3
find_package(Eigen3 3.2.2 REQUIRED)
## Define Eigen addons.
include(cmake/${PROJECT_NAME}-extras.cmake)
#########
# Build #
#########
# Add the library target
add_library(${PROJECT_NAME}
src/BufferRegion.cpp
src/GridMap.cpp
src/GridMapMath.cpp
src/Polygon.cpp
src/SubmapGeometry.cpp
src/iterators/CircleIterator.cpp
src/iterators/EllipseIterator.cpp
src/iterators/GridMapIterator.cpp
src/iterators/LineIterator.cpp
src/iterators/PolygonIterator.cpp
src/iterators/SlidingWindowIterator.cpp
src/iterators/SubmapIterator.cpp
)
# set target include directories
target_include_directories(${PROJECT_NAME}
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
${EIGEN3_INCLUDE_DIR}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
# add an alias
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# set target compile options
target_compile_options(${PROJECT_NAME}
PRIVATE
$<$<CONFIG:Debug>:-Werror>
)
###########
# Install #
###########
# 'make install' to the right locations
install(TARGETS ${PROJECT_NAME}
EXPORT "${PROJECT_NAME}Targets"
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION include
)
# This makes the project importable from the install directory
# Put config file in per-project dir.
install(EXPORT "${PROJECT_NAME}Targets"
FILE "${PROJECT_NAME}Targets.cmake"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION lib/cmake/${PROJECT_NAME})
# generate config.cmake
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
VERSION "${PROJECT_NAME}_VERSION"
COMPATIBILITY SameMajorVersion
)
# install config.cmake files
install(FILES "${PROJECT_NAME}Config.cmake"
DESTINATION "lib/cmake/${PROJECT_NAME}")
###########
# Testing #
###########
and the grid_map_coreConfig.cmake as follows:
include(CMakeFindDependencyMacro)
find_dependency(Eigen3 REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/grid_map_coreTargets.cmake")
and the test_project's CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(test_project)
set(CMAKE_MODULE_PATH /usr/share/cmake-3.0/Modules)
add_compile_options(-std=c++11)
find_package(grid_map_core REQUIRED CONFIG)
add_executable(test_project main.cpp)
target_link_libraries(test_project
PRIVATE
grid_map_core::grid_map_core
)
For completeness I'm adding the FindEigen3.cmake file:
# - Try to find Eigen3 lib
#
# This module supports requiring a minimum version, e.g. you can do
# find_package(Eigen3 3.1.2)
# to require version 3.1.2 or newer of Eigen3.
#
# Once done this will define
#
# EIGEN3_FOUND - system has eigen lib with correct version
# EIGEN3_INCLUDE_DIR - the eigen include directory
# EIGEN3_VERSION - eigen version
# Copyright (c) 2006, 2007 Montel Laurent, <montel#kde.org>
# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael#free.fr>
# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1#gmail.com>
# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
if(NOT Eigen3_FIND_VERSION)
if(NOT Eigen3_FIND_VERSION_MAJOR)
set(Eigen3_FIND_VERSION_MAJOR 2)
endif(NOT Eigen3_FIND_VERSION_MAJOR)
if(NOT Eigen3_FIND_VERSION_MINOR)
set(Eigen3_FIND_VERSION_MINOR 91)
endif(NOT Eigen3_FIND_VERSION_MINOR)
if(NOT Eigen3_FIND_VERSION_PATCH)
set(Eigen3_FIND_VERSION_PATCH 0)
endif(NOT Eigen3_FIND_VERSION_PATCH)
set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
endif(NOT Eigen3_FIND_VERSION)
macro(_eigen3_check_version)
file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
set(EIGEN3_VERSION_OK FALSE)
else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
set(EIGEN3_VERSION_OK TRUE)
endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
if(NOT EIGEN3_VERSION_OK)
message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
"but at least version ${Eigen3_FIND_VERSION} is required")
endif(NOT EIGEN3_VERSION_OK)
endmacro(_eigen3_check_version)
if (EIGEN3_INCLUDE_DIR)
# in cache already
_eigen3_check_version()
set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
else (EIGEN3_INCLUDE_DIR)
find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
PATHS
${CMAKE_INSTALL_PREFIX}/include
${KDE4_INCLUDE_DIR}
PATH_SUFFIXES eigen3 eigen
)
if(EIGEN3_INCLUDE_DIR)
_eigen3_check_version()
endif(EIGEN3_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
mark_as_advanced(EIGEN3_INCLUDE_DIR)
endif(EIGEN3_INCLUDE_DIR)
The error message you got is generated by find_dependency macro, but not by find_package command, which is called internally by that macro. Because you call find_dependency with REQUIRED keyword (and this keyword is passed to inner find_package), the only possible scenario of your error is follow:
Call to find_package(Eigen3 REQUIRED) interprets Eigen3 to be found.
But when find_dependency checks find_package results, it interprets Eigen3 to be not found.
Rather strange, isn't it?
Actually, your FindEigen3.cmake script is one of the old ones, which sets upper case flow of "FOUND" variable, which denotes success of the script:
# EIGEN3_FOUND - system has eigen lib with correct version
but the correct name of such variable should be Eigen3_FOUND (the package name should be exactly the same as in find_package(Eigen3) call and in the name of the script FindEigen3.cmake).
Command find_package checks both spelling of the "FOUND" variable: correct one and upper-case one. Such way, when FindEigen3.cmake script sets EIGEN3_FOUND variable with intention "I have found the package", find_package understands that intention, marking the package as found.
But macro find_dependency checks only correct name of the variable, Eigen3_FOUND. Because this variable isn't set by the FindEigen3.cmake script, the package is treated as not found.
As a fast fix, you may replace in the FindEigen3.cmake script EIGEN3_FOUND with Eigen3_FOUND, and everything should work. (Well, the same replacement should be done on the user machine. Or you should ship corrected FindEigen3.cmake script with your library).

cmake link error on linking shared library hierarchy to executable

In my project I have the following configuration:
low level shared library
shared library that is a wrapper of low level shared library
executable
so the files tree is :
CMakeLists.txt
SharedLibraryBase
CMakeLists.txt
inc
myLibBase.h -> defines MyLibBaseFunction()
src
myLibBase.cpp -> implements MyLibBaseFunction()
MySharedLibrary
CMakeLists.txt
inc
myLib.h -> defines MyLibFunction()
src
myLib.cpp -> implements MyLibFunction()
Executables
CMakeLists.txt
inc
main.h
src
main.cpp -> calls MyLibFunction()
When I perform make , I get a following link error:
ld.exe: cannot find -lMySharedLibrary
the main CMakeLists.txt is:
cmake_minimum_required(VERSION 3.5)
project(TestSharedLibraryProject)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "my_outputs")
message("Binary tree path : ${PROJECT_BINARY_DIR}")
add_subdirectory(SharedLibraryBase)
add_subdirectory(MySharedLibrary)
add_subdirectory(Executables)
the low level shared library CMakeLists.txt is:
###########################
# SharedLibraiesBase #
###########################
project(SharedLibraryBase)
# include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/inc)
# find source files
set(${PROJECT_NAME}_headers inc/myLibBase.h)
set(${PROJECT_NAME}_sources src/myLib.cpp)
# create shared library
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_headers} ${${PROJECT_NAME}_sources})
# add files to export to 'include'
set_property(TARGET ${PROJECT_NAME} PROPERTY PUBLIC_HEADER inc/myLibBase.h)
install (TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
PUBLIC_HEADER DESTINATION include)
the wrapper shared library CMakeLists.txt is:
#######################
# SharedLibraies #
#######################
project(MySharedLibrary)
# include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS} inc ../SharedLibraryBase/inc)
# find source files
set(${PROJECT_NAME}_headers inc/myLib.h)
set(${PROJECT_NAME}_sources src/myLib.cpp)
# create shared library
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_headers} ${${PROJECT_NAME}_sources})
#dependency shared library (from this project)
target_link_libraries(${PROJECT_NAME} SharedLibraryBase)
# add files to export to 'include'
set_property(TARGET ${PROJECT_NAME} PROPERTY PUBLIC_HEADER inc/myLib.h)
install (TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
PUBLIC_HEADER DESTINATION include)
the executable CMakeLists.txt is:
####################
# Executables #
####################
project(Executables)
# include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIRS}/inc ${CMAKE_CURRENT_SOURCE_DIR}/../MySharedLibrary/inc)
set(${PROJECT_NAME}_headers inc/main.h)
set(${PROJECT_NAME}_sources src/main.cpp)
#create executable
add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_headers} ${${PROJECT_NAME}_sources})
#dependency shared library (from this project)
target_link_libraries(${PROJECT_NAME} MyShraedLibrary)
install (TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
You have ordinary type error MyShraedLibrary instead of MySharedLibrary