config-file cmake包中变量的奇怪问题
我们可以使用cmake配置文件导入目标。
例如,给定的机械包括 foobarConfig.cmake.in
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}")
用于导出目标foobar
for an exported target foobar
可以做到:
find_package(foobar)
add_executable(usesfoo
usesfoo.cpp)
target_link_libraries(usesfoo
${FOOBAR_LIBRARY})
target_include_directories(usesfoo PUBLIC
${FOOBAR_INCLUDE_DIR})
通常正常。
但是,我遇到了一个奇怪的情况,在find_package之后,Config.cmake中设置的变量不可用。
例如:
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}")
输出为:
foobar include location: /test-import/opt/foobar/include
foobar library location: /test-import/opt/foobar/lib
found foobar
foobar include location2:
foobar library location2:
可能是
我如何:
- 查找这个问题?
- 将来避免类似的问题吗?
- 以安全且规范的方式创建这些文件吗?
尝试调试它时我非常困惑,并开始质疑Config程序包应该如何工作。
我应该使用导入目标的属性而不是变量吗?
find_package在什么范围内运行?我认为这就像一个include()而不是add_subdirectory(),它引入了自己的作用域。
这些变量如何变为未设置状态?
find_package的作用是什么?
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?
另请参见设置已安装软件包的导入cmake目标的位置。
该问题包含用于重现该问题的代码,类似于该问题的代码。
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:
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:
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
这是@havogt请求生成的Config.cmake我认为这不是标准代码,因为它是标准生成的代码:
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由find_package()的实现设置,而不是由Config.cmake它加载。添加check_required_components()是出于其他原因的良好做法(例如,有人认为该软件包不是非组件化的),但与此问题无关。
'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.
糟糕。这很尴尬。我已经将生成代码移到了shell脚本中,却忘记了转义变量!
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.
要回答我自己的问题:
如何找到这个问题?
将来避免类似的问题吗?
以安全且规范的方式创建这些文件吗?
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
- 将问题减少到最小的可重现示例(最好是在堆栈溢出之前发布)
- 避免(或至少要格外小心)从shell脚本生成代码
- 减轻压力并获得更多睡眠
- 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