Modern CMake transitive Dependency not found - cmake

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).

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 imported shared library links with relative path, breaks install

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)

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.

Include these variables into cmake

This scientific application I am using uses cmake to build its program. Attached is the CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
set(PROJECT_NAME myproject)
project(${PROJECT_NAME})
# Set verbose output while testing CMake
#set(CMAKE_VERBOSE_MAKEFILE 1)
# Set CMake behavior
cmake_policy(SET CMP0004 OLD)
# Get DOLFIN configuration data (DOLFINConfig.cmake must be in
# DOLFIN_CMAKE_CONFIG_PATH)
find_package(DOLFIN)
# Need to get VTK config because VTK uses advanced VTK features which
# mean it's not enough to just link to the DOLFIN target. See
# http://www.vtk.org/pipermail/vtk-developers/2013-October/014402.html
find_package(VTK HINTS ${VTK_DIR} $ENV{VTK_DIR} NO_MODULE QUIET)
# Default build type (can be overridden by user)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: Debug MinSizeRel Release RelWithDebInfo." FORCE)
endif()
# Compiler definitions
add_definitions(${DOLFIN_CXX_DEFINITIONS})
# Compiler flags
set(CMAKE_CXX_FLAGS "${DOLFIN_CXX_FLAGS} ${CMAKE_CXX_FLAGS}")
# Include directories
include_directories(${DOLFIN_INCLUDE_DIRS})
include_directories(SYSTEM ${DOLFIN_3RD_PARTY_INCLUDE_DIRS})
# Executable
add_executable(${PROJECT_NAME} main.cpp)
# Target libraries
target_link_libraries(${PROJECT_NAME} ${DOLFIN_LIBRARIES})
However, I need to include some additional compiler flags and libraries not included within DOLFIN. I need to incorporate this package called PAPI. On my system, the paths/environments are:
PAPI_LIBDIR=/project/cacds/apps/papi/5.4.0/lib
PAPI_INCLUDE=/project/cacds/apps/papi/5.4.0/include
PAPI_FLAG=-lpapi
Normally I would do something like this:
gcc -O3 -o -I myproject $(PAPI_LIBDIR) main.c -L $(PAPI_INCLUDE) $(PAPI_FLAG)
But again I need the additional DOLFIN libraries and third party packages. Pardon me if this is a silly or simple question, but how would I modify the above CMakeLists.txt? I am kind of new to cmake, so any help appreciated, thanks
The cleanest solution would be to create a FindPAPI.cmake. So, add to your project this file inside of a cmake/folder:
FindPAPI.cmake
# Try to find PAPI headers and libraries.
#
# Usage of this module as follows:
#
# find_package(PAPI)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# PAPI_PREFIX Set this variable to the root installation of
# libpapi if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# PAPI_FOUND System has PAPI libraries and headers
# PAPI_LIBRARIES The PAPI library
# PAPI_INCLUDE_DIRS The location of PAPI headers
find_path(PAPI_PREFIX
NAMES include/papi.h
)
find_library(PAPI_LIBRARIES
# Pick the static library first for easier run-time linking.
NAMES libpapi.a papi
HINTS ${PAPI_PREFIX}/lib ${HILTIDEPS}/lib
)
find_path(PAPI_INCLUDE_DIRS
NAMES papi.h
HINTS ${PAPI_PREFIX}/include ${HILTIDEPS}/include
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PAPI DEFAULT_MSG
PAPI_LIBRARIES
PAPI_INCLUDE_DIRS
)
mark_as_advanced(
PAPI_PREFIX_DIRS
PAPI_LIBRARIES
PAPI_INCLUDE_DIRS
)
Then, modify your CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
set(PROJECT_NAME myproject)
project(${PROJECT_NAME})
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(PAPI REQUIRED)
...
# Include directories
include_directories(${DOLFIN_INCLUDE_DIRS})
include_directories(SYSTEM ${DOLFIN_3RD_PARTY_INCLUDE_DIRS})
include_directories(${PAPI_INCLUDE_DIRS})
# Executable
add_executable(${PROJECT_NAME} main.cpp)
# Target libraries
target_link_libraries(${PROJECT_NAME} ${PAPI_LIBRARIES} ${DOLFIN_LIBRARIES})
I hope it resolves it! ;)

How to check if find_package found the package (boost)

I want to not add boost.cxx if cmake find_package found no boost installed. Does find_package return something that I can wrap in condition to compile boost.cxx or not. Here is my current cmake file:
add_executable (complex complex.cxx lexer.cxx boost.cxx ../../src/lili.cxx ../../src/lilu.cxx)
# Make sure the compiler can find all include files
include_directories (../../src)
include_directories (.)
# Make sure the linker can find all needed libraries
# rt: clock_gettime()
target_link_libraries(complex rt)
# Install example application
install (TARGETS complex
RUNTIME DESTINATION bin)
IF(UNIX)
find_package(Boost COMPONENTS system filesystem REQUIRED)
## Compiler flags
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-O2")
set(CMAKE_EXE_LINKER_FLAGS "-lsqlite3 -lrt -lpthread")
endif()
target_link_libraries(complex
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
#${PROTOBUF_LIBRARY}
)
ENDIF(UNIX)
The FindXXX scripts are supposed to set a variable <Packagename>_FOUND to TRUEif the package was found. So in your case, it will set Boost_FOUND if boost was found.
When compiling your Boost.cxx, I assume that you will need Boost headers as well, so you should adjust your include directories as well.*
look for Boost before creating your executable. Furhtermore, you need to set your include directories before adding the executable.
IF(UNIX)
find_package(Boost COMPONENTS system filesystem REQUIRED)
# IF( Boost_FOUND ) # checking this variable isnt even necessary, since you added
# REQUIRED to your call to FIND_PACKAGE
SET( BOOST_SRC_FILES boost.cxx )
INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIRS} ) # you could move this down as well
# as ${Boost_INCLUDE_DIRS} will be
# empty if Boost was not found
# ENDIF()
ENDIF()
add_executable (complex complex.cxx lexer.cxx ${BOOST_SRC_FILES} ../../src/lili.cxx ../../src/lilu.cxx)
# Make sure the compiler can find all include files
include_directories (../../src)
include_directories (.)
# INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIRS} ) # alternative location to
# add include dirs, see above
# Make sure the linker can find all needed libraries
# rt: clock_gettime()
target_link_libraries(complex rt)
# Install example application
install (TARGETS complex
RUNTIME DESTINATION bin)
IF(UNIX)
## Compiler flags
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-O2")
set(CMAKE_EXE_LINKER_FLAGS "-lsqlite3 -lrt -lpthread")
endif()
target_link_libraries(complex
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
#${PROTOBUF_LIBRARY}
)
ENDIF(UNIX)
Afternote: Since you use the REQUIRED flag when looking for Boost (since you only need it on Unix platform) it is even sufficient to use the optional-source-files-in-a-variable trick.
(*) Thanks to your question, I just found out that it doesn't matter whether include_directories(...) is called before or after creating the target with ADD_EXECUTABLE or ADD_LIBRARY since the directories are added to all targets in the same project.
Yes, if the find_package(Boost COMPONENTS system filesystem REQUIRED) succeeds, Boost_FOUND will be true.
Also, there will be component-specific versions, so Boost_date_time_FOUND, Boost_filesystem_FOUND, etc.
For further info, run
cmake --help-module FindBoost
Yes, it sets variable Boost_FOUND. Example from FindBoost.cmake:
== Using actual libraries from within Boost: ==
#
# set(Boost_USE_STATIC_LIBS ON)
# set(Boost_USE_MULTITHREADED ON)
# set(Boost_USE_STATIC_RUNTIME OFF)
# find_package( Boost 1.36.0 COMPONENTS date_time filesystem system ... )
#
# if(Boost_FOUND)
# include_directories(${Boost_INCLUDE_DIRS})
# add_executable(foo foo.cc)
# target_link_libraries(foo ${Boost_LIBRARIES})
# endif()