使用CMake配置文件简化库的导入

0. 引言

在为我们的库编写CMake时,我们确保它能够正确编译。然而,库的用户需要编写一些CMake代码来查找包含的头文件、共享/静态库和可执行文件。这对于库的用户来说可能是一项麻烦的任务。作为库的开发者,我们可以在CMake脚本中添加几行模板代码,使用户只需使用一行find_package()命令即可轻松导入所有内容。

在这篇文章中,我将通过一个示例展示如何为库创建版本文件和配置文件,以便能方便地导入到其他项目中。

1. 示例

示例代码可以在GitHub上找到。它是一个共享库,文件结构如下:

--- geometry
    |
    ---- shape
    |      |
    |      --- shape.h, shape.cpp
    |      |
    |      --- CMakeLists.txt
    |
    ---- square
    |      |
    |      --- square.h, square.cpp, info.h
    |      |
    |      --- CMakeLists.txt
    |
    ---- example
    |      |
    |      --- app.cpp
    |
    ---- CMakeLists.txt

CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.23)
project(geometry LANGUAGES CXX VERSION 5.4.3)

if (MSVC)
    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()

add_library(geo SHARED)
target_include_directories(geo PRIVATE "${PROJECT_SOURCE_DIR}")
add_subdirectory("shape")
add_subdirectory("square")

install(TARGETS geo 
    EXPORT geoTargets
    FILE_SET HEADERS
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include)

install(EXPORT geoTargets
    FILE geoTargets.cmake
    NAMESPACE geo::
    DESTINATION lib/cmake/geo)

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    "geoConfigVersion.cmake"
    VERSION ${geo_VERSION}
    COMPATIBILITY AnyNewerVersion)

install(FILES "geoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/geoConfigVersion.cmake"
    DESTINATION lib/cmake/geo)

在这个示例中,目标和依赖项的解释在之前的文章中已有说明,因此我将继续讲解。

在这里插入图片描述

3. 关键命令解析

  • install(TARGETS geo EXPORT geoTargets ...):这里的EXPORT定义了geoTargets变量。它表示如果你需要导出geo的目标,请使用geoTargets

  • install(EXPORT geoTargets):这是前一个安装命令的配对,我们定义了要导出目标文件的名称、命名空间以及安装位置。命名空间是加载到其他项目时添加到包名称的前缀。因此,geo目标在其他项目中将被视为geo::geo

  • include(CMakePackageConfigHelpers):这是CMake加载的一个模块,用于创建配置文件。

  • write_basic_package_version_file():该函数创建geoConfigVersion.cmake文件,存储包的版本信息及其兼容性。AnyNewerVersion表示如果包的版本是请求版本的同一版本或更高版本,CMake将导入该包。有关更多详细信息,请参见CMake手册。

  • install(FILES...):将配置文件和版本文件复制到安装目录。版本文件由此脚本创建,而geoConfig.cmake则是我们自己编写的。

geometry/geoConfig.cmake 文件如下所示:

include(CMakeFindDependencyMacro)
# find_dependency(xxx 2.0)
include(${CMAKE_CURRENT_LIST_DIR}/geoTargets.cmake)

我注释掉了find_dependency()行,因为这个示例没有其他依赖项。如果你的包有其他依赖项,需要在这里提及

4. 构建和安装

现在我们可以编译和安装库。在源目录中打开终端,运行以下命令:

mkdir build
cd build

对于单配置生成器(如make或Ninja):

cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . # CMake提供的一个命令,用于调用生成的构建系统(如Makefile、Ninja等)来编译项目
cmake --install . --prefix ../../geo-install # 安装构建好的项目,--prefix ../../geo-install指定了安装的目标目录

对于多配置生成器(如VS C++或Xcode):

cmake ..
cmake --build . --config release
cmake --install . --prefix ../../geo-install --config release

对应的geoTargets.cmake文件,的主要作用是定义和管理一个名为 geo::geo 的导入目标。这个文件通常是在构建和安装库时自动生成的,目的是在其他项目中使用这个库时能够方便地导入和链接。

# Generated by CMake

if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
   message(FATAL_ERROR "CMake >= 2.8.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "2.8.3")
   message(FATAL_ERROR "CMake >= 2.8.3 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 2.8.3...3.25)
#----------------------------------------------------------------
# Generated CMake target import file.
#----------------------------------------------------------------

# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)

# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS geo::geo)
  list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
  if(TARGET "${_cmake_expected_target}")
    list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
  else()
    list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
  endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
  unset(_cmake_targets_defined)
  unset(_cmake_targets_not_defined)
  unset(_cmake_expected_targets)
  unset(CMAKE_IMPORT_FILE_VERSION)
  cmake_policy(POP)
  return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
  string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
  string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
  message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)


# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
  set(_IMPORT_PREFIX "")
endif()

# Create imported target geo::geo
add_library(geo::geo SHARED IMPORTED)

set_target_properties(geo::geo PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
)

if(NOT CMAKE_VERSION VERSION_LESS "3.23.0")
  target_sources(geo::geo
    INTERFACE
      FILE_SET "HEADERS"
      TYPE "HEADERS"
      BASE_DIRS "${_IMPORT_PREFIX}/include"
      FILES "${_IMPORT_PREFIX}/include/shape/shape.h" "${_IMPORT_PREFIX}/include/square/square.h"
  )
else()
  set_property(TARGET geo::geo
    APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
      "${_IMPORT_PREFIX}/include"
  )
endif()

# Load information for each installed configuration.
file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/geoTargets-*.cmake")
foreach(_cmake_config_file IN LISTS _cmake_config_files)
  include("${_cmake_config_file}")
endforeach()
unset(_cmake_config_file)
unset(_cmake_config_files)

# Cleanup temporary variables.
set(_IMPORT_PREFIX)

# Loop over all imported files and verify that they actually exist
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
  foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
    if(NOT EXISTS "${_cmake_file}")
      message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
   \"${_cmake_file}\"
but this file does not exist.  Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
   \"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
    endif()
  endforeach()
  unset(_cmake_file)
  unset("_cmake_import_check_files_for_${_cmake_target}")
endforeach()
unset(_cmake_target)
unset(_cmake_import_check_targets)

# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.

# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)

…详情请参照古月居

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敢敢のwings

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值