首页 > 解决方案 > 为已安装的包正确设置导入的 cmake 目标的位置

问题描述

我希望能够从已安装的库中导入目标,但是在使用时:

install(TARGETS
        foobar
        EXPORT foobarLibTargets
        LIBRARY DESTINATION lib)

cmake 生成一个包含绝对路径的foobarLibTargets.cmake :

set_target_properties(foobar PROPERTIES
        IMPORTED_LOCATION_NOCONFIG "/where/I/happened/to/build/libfoobar.so"
        IMPORTED_SONAME_NOCONFIG "libfoobar.so"
)

这样使用安装中导入的目标的构建将失败,因为路径不存在。

Q 我怎样才能让它使用正确的相对位置呢?

这相当于:

set_target_properties(foobar PROPERTIES
                      IMPORTED_LOCATION_NOCONFIG "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so")

如果我看另一个做类似但有效的项目,它有:

set_target_properties(foobar PROPERTIES
         IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libfoobar.so"
         IMPORTED_SONAME_RELEASE "libfoobar.so"
)

以下是一些重现该问题的示例文件:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.7)
project(FOOBAR VERSION 1.2.3)
set(VERSION 1.2.3)

set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
set(CMAKE_INSTALL_PREFIX "/opt/foobar" CACHE PATH "Install path prefix" FORCE)

add_library(foobar SHARED
  foobar.cpp
)

set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_NAME "foobar")

set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")

include(CPack)

# Indicate the content of the distribution pakcages
install(FILES
  ${CMAKE_SOURCE_DIR}/foobar.h
  DESTINATION include
)
install(TARGETS
  foobar
  EXPORT foobarLibTargets
  LIBRARY DESTINATION lib)

include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/foobar)
set(INCLUDE_INSTALL_DIR include)
set(LIBRARY_INSTALL_DIR lib)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
configure_package_config_file(foobarConfig.cmake.in
     "${CMAKE_BINARY_DIR}/foobarConfig.cmake"
    INSTALL_DESTINATION "${ConfigFileInstallDir}"
    PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR)
write_basic_package_version_file(
   "${CMAKE_BINARY_DIR}/foobarConfigVersion.cmake"
   VERSION "${VERSION}"
   COMPATIBILITY ExactVersion)
export(EXPORT foobarLibTargets
       FILE "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake")
install(EXPORT foobarLibTargets
        FILE foobarTargets.cmake
        DESTINATION lib/cmake)
install(FILES
   "${CMAKE_CURRENT_BINARY_DIR}/foobarConfig.cmake"
   "${CMAKE_CURRENT_BINARY_DIR}/foobarConfigVersion.cmake"
   "${CMAKE_CURRENT_BINARY_DIR}/foobarLibTargets.cmake"
    DESTINATION "${ConfigFileInstallDir}")

foob​​arConfig.cmake.in:

set(FOOBAR_VERSION @VERSION@)

@PACKAGE_INIT@

set_and_check(FOOBAR_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
set_and_check(FOOBAR_LIBRARY "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so")
set_and_check(FOOBAR_LIBRARY_DIR "@PACKAGE_LIBRARY_INSTALL_DIR@")
include("${CMAKE_CURRENT_LIST_DIR}/foobarLibTargets.cmake")

# workaround - correct absolute path in the above
# this shouldn't be necessary (hence this question)
#set_target_properties(foobar PROPERTIES
#  IMPORTED_LOCATION_NOCONFIG "@PACKAGE_LIBRARY_INSTALL_DIR@/libfoobar.so"
#)

foob​​ar.h:

void hello();

foob​​ar.cpp:

#include <iostream>

void hello() {
   std::cerr << "hello world\n";
}

useFoo.cmake(使用已安装库的示例项目的 CMakeLists.txt):

cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
set(VERSION 1.2.3)

find_package(foobar)
file(GENERATE OUTPUT foobar-gen CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")

message(STATUS "FOOBAR_LIBRARY_DIR=${FOOBAR_LIBRARY_DIR}")
message(STATUS "FOOBAR_INCLUDE_DIR=${FOOBAR_INCLUDE_DIR}")

build.sh(构建并使用安装包):

#!/bin/sh

rm -rf target
mkdir target
cd target
cmake .. &&
make &&
cpack -G TGZ
if [ $? -ne 0 ]; then
   echo "doh!"
   exit 1
fi

cd ..
rm -rf install
mkdir install
cd install
tar -xvzf ../target/foobar-1.2.3.tar.gz
cp ../useFoo.cmake CMakeLists.txt
export CMAKE_PREFIX_PATH=`pwd`/opt/foobar/lib/cmake:`pwd`/opt/foobar/lib/cmake/foobar
cmake .
if [ $? -ne 0 ]; then
   echo "doh!"
   exit 1
fi
cat foobar-gen

的输出cat foobar-gen是:

<TARGET_FILE:foobar>=/where/I/happened/to/build/libfoobar.so

我希望它是:

<TARGET_FILE:foobar>=/where/I/actually/installed/libfoobar.so

如果我取消注释解决方法,它会变成什么。 有没有办法避免解决方法?

相关问题 -配置文件 cmake 包中变量的奇怪问题- 具有类似的代码,既重现了此问题,又在顶部添加了另一个问题。

标签: cmake

解决方案


主要问题是这两个文件都已安装foobarLibTargets.cmake并且foobarTargets.cmake选择了错误的文件。

您将在下面找到一个改进的项目以及更好地组织构建系统的注释。

ChangeLog 总结编辑

  • 2019-05-25
    • 创建 GitHub 项目以简化重用和适应。见https://github.com/jcfr/stackoverflow-56135785-answer
    • 将项目和源目录从 重命名foobarFooBarLib,相应地更新 Suggestions 部分
    • 提升build.sh
    • 更新的建议(CPACK_PACKAGING_INSTALL_PREFIX应该是绝对的)
    • 转速:
      • 添加对使用构建 RPM 包的支持make package
      • 更新build.sh以显示 RPM 包的内容

评论

  • 应该生成两个配置文件:
    • 一个用于构建树:这允许您的项目用户直接针对您的项目构建并导入目标
    • 一个用于安装树(最终也被打包)
  • 不要强求值CMAKE_INSTALL_PREFIX
  • CPACK_PACKAGING_INSTALL_PREFIX不应设置为绝对目录
  • 为了一致性起见,使用foobarTargets代替foobarLibTargets
  • <projecname_uc>下面使用的占位符对应于项目名称大写(ABC而不是abc
  • 要允许在与其他项目一起供应时配置您的项目,请首选使用<projecname_uc>_. 这意味着<projecname_uc>_INSTALL_LIBRARY_DIR优于LIBRARY_INSTALL_DIR
  • 要允许项目用户配置*_INSTALL_DIR变量,请将它们包装起来if(DEFINED ...)
  • 始终如一地使用变量(例如LIBRARY_INSTALL_DIR,应始终使用而不是lib
  • 更喜欢命名变量<projecname_uc>_INSTALL_*_DIR而不是<projecname_uc>_*_INSTALL_DIR,这样在阅读代码时更容易知道变量的用途。
  • 由于版本已经与项目相关联,因此无需设置VERSION变量。相反,您可以使用PROJECT_VERSIONFOOBAR_VERSION
  • 如果开始一个新项目,请选择最新的 CMake 版本。CMake 3.13 而不是 CMake 3.7
  • 引入变量<projecname_uc>_INSTALL_CONFIG_DIR
  • <project_name>Targets.cmake不应使用 安装install(FILES ...),它已与安装规则相关联
  • 条件设置CMAKE_INSTALL_RPATH,仅在Linux上有效
  • <project_name>Config.cmake.in
    • 无需设置FOOBAR_LIBRARY,此信息已与导出的foobar目标相关联
    • 也不需要 FOOBAR_LIBRARY_DIR,此信息已与导出的foobar目标相关联
    • 而不是设置 FOOBAR_INCLUDE_DIR,target_include_directories应该使用该命令
    • 删除设置FOOBAR_VERSION,生成版本文件已经负责设置版本。
  • 为目标声明安装规则时,始终指定 ARCHIVE、LIBRARY 和 RUNTIME。它避免了切换库类型时的问题。少一件事要考虑。
  • 始终使用您的安装规则指定组件。它允许您的项目的用户有选择地安装它的一部分,仅开发组件或仅运行时组件,...
  • 初始化CMAKE_BUILD_TYPE也很重要,它确保生成的 Targets 文件与配置相关联(而不是具有后缀-noconfig.cmake

建议的更改

一般来说,我建议有一个源代码树、一个构建树和安装树。下面发布的文件采用以下布局:

./build.sh

./FooBarLib/FooBarLibConfig.cmake.in
./FooBarLib/CMakeLists.txt
./FooBarLib/foobar.cpp
./FooBarLib/foobar.h

./FooBarLib-build

./FooBarLib-install

./useFoo/CMakeLists.txt
./useFoo-build
  • 构建.sh
#!/bin/bash

set -xeu
set -o pipefail

script_dir=$(cd $(dirname $0) || exit 1; pwd)

project_name=FooBarLib
archive_name=${project_name}

# cleanup ${project_name}-build
cd $script_dir
rm -rf ${project_name}-build
mkdir ${project_name}-build
cd ${project_name}-build

# configure, build and package ${project_name}
cmake ../${project_name}
make
make package # equivalent to running "cpack -G TGZ" and "cmake -G RPM"

# extract ${project_name} archive
cd $script_dir
rm -rf ${project_name}-install
mkdir ${project_name}-install
cd ${project_name}-install
tar -xvzf ../${project_name}-build/${archive_name}-1.2.3.tar.gz

# cleanup useFoo-build
cd $script_dir
rm -rf useFoo-build
mkdir useFoo-build
cd useFoo-build

cpack_install_prefix=/opt

# configure useFoo
cmake -D${project_name}_DIR=$script_dir/${project_name}-install${cpack_install_prefix}/lib/cmake/${project_name}/ ../useFoo

cat foobar-gen

# display content of RPM. If command "rpmbuild" is available, RPM package is expected.
if command -v rpmbuild &> /dev/null; then
  rpm -qlp $script_dir/${project_name}-build/${archive_name}-1.2.3.rpm
fi
  • FooBarLib/CMakeLists.txt
cmake_minimum_required(VERSION 3.13)

project(FooBarLib VERSION 1.2.3)

if(UNIX AND NOT APPLE)
  set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/")
endif()

#------------------------------------------------------------------------------
# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "Setting build type to 'Release' as none was specified.")
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
  mark_as_advanced(CMAKE_BUILD_TYPE)
  # Set the possible values of build type for cmake-gui
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

#------------------------------------------------------------------------------
# This variable controls the prefix used to generate the following files:
#  <export_config_name>ConfigVersion.cmake
#  <export_config_name>Config.cmake
#  <export_config_name>Targets.cmake
# and it also used to initialize FOOBARLIB_INSTALL_CONFIG_DIR value.
set(export_config_name ${PROJECT_NAME})

#------------------------------------------------------------------------------
if(NOT DEFINED FOOBARLIB_INSTALL_INCLUDE_DIR)
  set(FOOBARLIB_INSTALL_INCLUDE_DIR include)
endif()

if(NOT DEFINED FOOBARLIB_INSTALL_BIN_DIR)
  set(FOOBARLIB_INSTALL_BIN_DIR bin)
endif()

if(NOT DEFINED FOOBARLIB_INSTALL_LIBRARY_DIR)
  set(FOOBARLIB_INSTALL_LIBRARY_DIR lib)
endif()

if(NOT DEFINED FOOBARLIB_INSTALL_CONFIG_DIR)
  set(FOOBARLIB_INSTALL_CONFIG_DIR ${FOOBARLIB_INSTALL_LIBRARY_DIR}/cmake/${export_config_name})
endif()

#------------------------------------------------------------------------------
set(headers
  foobar.h
  )

# Install rule for headers
install(
  FILES ${headers}
  DESTINATION ${FOOBARLIB_INSTALL_INCLUDE_DIR}
  COMPONENT Development
  )

#------------------------------------------------------------------------------
add_library(foobar SHARED
  foobar.cpp
  )

target_include_directories(foobar
  PUBLIC
    $<BUILD_INTERFACE:${FooBarLib_SOURCE_DIR}>
    $<INSTALL_INTERFACE:${FOOBARLIB_INSTALL_INCLUDE_DIR}>
  )

install(
  TARGETS foobar
  EXPORT ${export_config_name}Targets
  ARCHIVE DESTINATION ${FOOBARLIB_INSTALL_LIBRARY_DIR} COMPONENT Development
  LIBRARY DESTINATION ${FOOBARLIB_INSTALL_LIBRARY_DIR} COMPONENT RuntimeLibraries
  RUNTIME DESTINATION ${FOOBARLIB_INSTALL_BIN_DIR} COMPONENT RuntimeLibraries
  )

#------------------------------------------------------------------------------
# Configure <export_config_name>ConfigVersion.cmake common to build and install tree
include(CMakePackageConfigHelpers)
set(config_version_file ${PROJECT_BINARY_DIR}/${export_config_name}ConfigVersion.cmake)
write_basic_package_version_file(
  ${config_version_file}
  VERSION "${FooBarLib_VERSION}"
  COMPATIBILITY ExactVersion
  )

#------------------------------------------------------------------------------
# Export '<export_config_name>Targets.cmake' for a build tree
export(
  EXPORT ${PROJECT_NAME}Targets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/${export_config_name}Targets.cmake"
  )

# Configure '<export_config_name>Config.cmake' for a build tree
set(build_config ${CMAKE_BINARY_DIR}/${export_config_name}Config.cmake)
configure_package_config_file(
  ${export_config_name}Config.cmake.in 
  ${build_config}
  INSTALL_DESTINATION "${PROJECT_BINARY_DIR}"
  )

#------------------------------------------------------------------------------
# Export '<export_config_name>Targets.cmake' for an install tree
install(
  EXPORT ${export_config_name}Targets
  FILE ${export_config_name}Targets.cmake
  DESTINATION ${FOOBARLIB_INSTALL_CONFIG_DIR}
  )

set(install_config ${PROJECT_BINARY_DIR}/CMakeFiles/${export_config_name}Config.cmake)
configure_package_config_file(
  ${export_config_name}Config.cmake.in 
  ${install_config}
  INSTALL_DESTINATION ${FOOBARLIB_INSTALL_CONFIG_DIR}
  )

# Install config files
install(
  FILES ${config_version_file} ${install_config}
  DESTINATION "${FOOBARLIB_INSTALL_CONFIG_DIR}"
  )

#------------------------------------------------------------------------------
# Generate package

set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")

# Setting this variable also impacts the layout of TGZ.
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt")

# Setting CPACK_SOURCE_* and CPACK_GENERATOR allow to have "make package" generates
# the expected archive.

# Disable source generator enabled by default
set(CPACK_SOURCE_TBZ2 OFF CACHE BOOL "Enable to build TBZ2 source packages" FORCE)
set(CPACK_SOURCE_TGZ  OFF CACHE BOOL "Enable to build TGZ source packages" FORCE)
set(CPACK_SOURCE_TZ OFF CACHE BOOL "Enable to build TZ source packages" FORCE)

# Select generators
if(UNIX AND NOT APPLE)
  set(CPACK_GENERATOR "TGZ")
  find_program(RPMBUILD_PATH rpmbuild)
  if(RPMBUILD_PATH)
    list(APPEND CPACK_GENERATOR "RPM")
  endif()
elseif(APPLE)
  # ...
endif()

include(CPack)
  • FooBarLib/FooBarLibConfig.cmake.in
@PACKAGE_INIT@

set(export_config_name "@export_config_name@")

set_and_check(${export_config_name}_TARGETS "${CMAKE_CURRENT_LIST_DIR}/${export_config_name}Targets.cmake")

include(${${export_config_name}_TARGETS})
  • useFoo/CMakeLists.txt
cmake_minimum_required(VERSION 3.13)

project(useFoo VERSION 1.2.3)

find_package(FooBarLib REQUIRED)

file(GENERATE OUTPUT foobar-gen CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")

get_target_property(foobar_INCLUDE_DIR foobar INTERFACE_INCLUDE_DIRECTORIES)
message(STATUS "foobar_INCLUDE_DIR=${foobar_INCLUDE_DIR}")

get_target_property(imported_location foobar IMPORTED_LOCATION_RELEASE)
get_filename_component(foobar_LIBRARY_DIR ${imported_location} DIRECTORY)
message(STATUS "foobar_LIBRARY_DIR=${foobar_LIBRARY_DIR}")

build.sh 的输出

./build.sh 
+ set -o pipefail
+++ dirname ./build.sh
++ cd .
++ pwd
+ script_dir=/tmp/stackoverflow-56135785-answer
+ project_name=FooBarLib
+ archive_name=FooBarLib
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf FooBarLib-build
+ mkdir FooBarLib-build
+ cd FooBarLib-build
+ cmake ../FooBarLib
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to 'Release' as none was specified.
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/stackoverflow-56135785-answer/FooBarLib-build
+ make
Scanning dependencies of target foobar
[ 50%] Building CXX object CMakeFiles/foobar.dir/foobar.cpp.o
[100%] Linking CXX shared library libfoobar.so
[100%] Built target foobar
+ make package
[100%] Built target foobar
Run CPack packaging tool...
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: FooBarLib
CPack: - Install project: FooBarLib
CPack: Create package
CPack: - package: /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.tar.gz generated.
CPack: Create package using RPM
CPack: Install projects
CPack: - Run preinstall target for: FooBarLib
CPack: - Install project: FooBarLib
CPack: Create package
-- CPackRPM:Debug: Using CPACK_RPM_ROOTDIR=/tmp/stackoverflow-56135785-answer/FooBarLib-build/_CPack_Packages/Linux/RPM
CPackRPM: Will use GENERATED spec file: /tmp/stackoverflow-56135785-answer/FooBarLib-build/_CPack_Packages/Linux/RPM/SPECS/foobarlib.spec
CPack: - package: /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.rpm generated.
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf FooBarLib-install
+ mkdir FooBarLib-install
+ cd FooBarLib-install
+ tar -xvzf ../FooBarLib-build/FooBarLib-1.2.3.tar.gz
opt/
opt/include/
opt/include/foobar.h
opt/lib/
opt/lib/libfoobar.so
opt/lib/cmake/
opt/lib/cmake/FooBarLib/
opt/lib/cmake/FooBarLib/FooBarLibTargets.cmake
opt/lib/cmake/FooBarLib/FooBarLibTargets-release.cmake
opt/lib/cmake/FooBarLib/FooBarLibConfigVersion.cmake
opt/lib/cmake/FooBarLib/FooBarLibConfig.cmake
+ cd /tmp/stackoverflow-56135785-answer
+ rm -rf useFoo-build
+ mkdir useFoo-build
+ cd useFoo-build
+ cpack_install_prefix=/opt
+ cmake -DFooBarLib_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib/cmake/FooBarLib/ ../useFoo
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- foobar_INCLUDE_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/include
-- foobar_LIBRARY_DIR=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/stackoverflow-56135785-answer/useFoo-build
+ cat foobar-gen
<TARGET_FILE:foobar>=/tmp/stackoverflow-56135785-answer/FooBarLib-install/opt/lib/libfoobar.so
+ command -v rpmbuild
+ rpm -qlp /tmp/stackoverflow-56135785-answer/FooBarLib-build/FooBarLib-1.2.3.rpm
/opt
/opt/include
/opt/include/foobar.h
/opt/lib
/opt/lib/cmake
/opt/lib/cmake/FooBarLib
/opt/lib/cmake/FooBarLib/FooBarLibConfig.cmake
/opt/lib/cmake/FooBarLib/FooBarLibConfigVersion.cmake
/opt/lib/cmake/FooBarLib/FooBarLibTargets-release.cmake
/opt/lib/cmake/FooBarLib/FooBarLibTargets.cmake
/opt/lib/libfoobar.so

推荐阅读