6

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.

Bruce Adams
  • 4,953
  • 4
  • 48
  • 111
  • A script which processes `find_package()` request is called in `include()`-like way, so every variable it sets is available for the caller. I would suggest you to verify, that **exactly your script** is called as response to `find_package(foobar)`: There is no `Findfoobar.cmake` script and there is no other `foobarConfig.cmake` scripts. You may also check (after `find_package()` call) the variable `foobar_DIR`: it should point exactly to the directory with your script. – Tsyvarev May 17 '19 at 19:36
  • From your explanation, I don't understand if you are able to reproduce the problem or if it just happens sometimes. If it is reproducible, please post everything that is needed to reproduce it. – havogt May 21 '19 at 06:11
  • In the case which is not working, can you also post the content of the generated `foobarConfig.cmake`. – havogt May 21 '19 at 06:14
  • I posted a complete example in the related question, fixed it and then that problem happened again on a different project and added this issue on top. I am going through the onerous task of reducing it to something simpler again but what I'm really looking for is some understanding of the mechanism that I'm missing. – Bruce Adams May 21 '19 at 09:15
  • I have managed to reduce the example enough to incude it in the question. – Bruce Adams May 21 '19 at 11:33

2 Answers2

3

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
Bruce Adams
  • 4,953
  • 4
  • 48
  • 111
0

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.

Tania Chistyakova
  • 3,928
  • 6
  • 13
  • I tried that already. It makes no difference. The problem is more subtle. – Bruce Adams May 21 '19 at 09:11
  • I am confused with you. 1. This is mandatory line, why it's not in your code from post? 2. foobarConfig.cmake.in is a template, where the code that generates actual config? – Tania Chistyakova May 21 '19 at 09:25
  • Adding check_required_components() is good practice but I don't think it is mandatory. It helps with picking up that someone thinks the package is componentised when it isn't but is not relevant to this issue. You can see from the generated config that the macro doesn't do much. – Bruce Adams May 21 '19 at 09:33
  • Try to add NO_POLICY_SCOPE in find_package or use set with PARENT_SCOPE – Tania Chistyakova May 21 '19 at 09:37
  • find_package() should operate in the scope its invoked. – Bruce Adams May 21 '19 at 09:52
  • There are two options of errors. 1. Variables get reverted because of an error. To be sure we need to see the environment. 2. Variable assigned in nested scope. To be sure we need to see a generation code. For the both cases your question isn't complete – Tania Chistyakova May 21 '19 at 09:59