Strange issue with variables in a config-file cmak

2020-02-14 03:35发布

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.

标签: cmake
2条回答
Emotional °昔
2楼-- · 2020-02-14 04:29

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.

查看更多
▲ chillily
3楼-- · 2020-02-14 04:31

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
查看更多
登录 后发表回答