导入和导出指南

简介

在本指南中,我们将介绍 IMPORTED 目标的概念,并演示如何将磁盘上现有的可执行文件或库文件导入 CMake 项目。然后,我们将展示 CMake 如何支持从一个基于 CMake 的项目导出目标并将其导入另一个项目。最后,我们将演示如何使用配置文件打包项目,以便轻松集成到其他 CMake 项目中。本指南和完整的示例源代码可在 CMake 源代码树的 Help/guide/importing-exporting 目录中找到。

导入目标

IMPORTED 目标用于将 CMake 项目之外的文件转换为项目内部的逻辑目标。IMPORTED 目标是使用 add_executable()add_library() 命令的 IMPORTED 选项创建的。不会为 IMPORTED 目标生成任何构建文件。一旦导入,IMPORTED 目标可以像项目中的任何其他目标一样被引用,并提供了一种方便、灵活的对外部可执行文件和库的引用。

默认情况下,IMPORTED 目标名称在其创建的目录及其子目录中具有作用域。我们可以使用 GLOBAL 选项来扩展可见性,以便目标在构建系统中全局可访问。

IMPORTED 目标的详细信息通过设置以 IMPORTED_INTERFACE_ 开头的属性来指定。例如,IMPORTED_LOCATION 包含磁盘上目标的完整路径。

导入可执行文件

首先,我们将通过一个简单的示例来演示如何创建一个 IMPORTED 可执行目标,然后从 add_custom_command() 命令中引用它。

我们需要进行一些设置才能开始。我们希望创建一个可执行文件,当它运行时,会在当前目录中创建一个基本的 main.cc 文件。这个项目的细节并不重要。导航到 Help/guide/importing-exporting/MyExe,创建一个构建目录,运行 cmake 并构建和安装项目。

$ cd Help/guide/importing-exporting/MyExe
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .
$ cmake --install . --prefix <install location>
$ <install location>/myexe
$ ls
[...] main.cc [...]

现在我们可以将此可执行文件导入另一个 CMake 项目。本节的源代码可在 Help/guide/importing-exporting/Importing 中找到。在 CMakeLists 文件中,使用 add_executable() 命令创建一个名为 myexe 的新目标。使用 IMPORTED 选项告诉 CMake 此目标引用项目外部的可执行文件。不会生成构建它的规则,并且 IMPORTED 目标属性将被设置为 true

add_executable(myexe IMPORTED)

接下来,使用 set_property() 命令设置目标的 IMPORTED_LOCATION 属性。这将告诉 CMake 磁盘上目标的位置。该位置可能需要调整为上一步中指定的 <安装位置>

set_property(TARGET myexe PROPERTY
             IMPORTED_LOCATION "../InstallMyExe/bin/myexe")

我们现在可以像项目中构建的任何目标一样引用此 IMPORTED 目标。在这种情况下,让我们假设我们希望在项目中使用生成的源文件。在 add_custom_command() 命令中使用 IMPORTED 目标

add_custom_command(OUTPUT main.cc COMMAND myexe)

由于 COMMAND 指定了一个可执行目标名称,它将自动替换为上面 IMPORTED_LOCATION 属性给出的可执行文件位置。

最后,使用 add_custom_command() 的输出

add_executable(mynewexe main.cc)

导入库

同样,可以通过 IMPORTED 目标访问其他项目的库。

注意:本节示例的完整源代码未提供,留作读者的练习。

在 CMakeLists 文件中,添加一个 IMPORTED 库并指定其在磁盘上的位置

add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY
             IMPORTED_LOCATION "/path/to/libfoo.a")

然后在我们的项目内部使用 IMPORTED

add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE foo)

在 Windows 上,.dll 及其 .lib 导入库可以一起导入

add_library(bar SHARED IMPORTED)
set_property(TARGET bar PROPERTY
             IMPORTED_LOCATION "c:/path/to/bar.dll")
set_property(TARGET bar PROPERTY
             IMPORTED_IMPLIB "c:/path/to/bar.lib")
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE bar)

具有多种配置的库可以导入为一个目标

find_library(math_REL NAMES m)
find_library(math_DBG NAMES md)
add_library(math STATIC IMPORTED GLOBAL)
set_target_properties(math PROPERTIES
  IMPORTED_LOCATION "${math_REL}"
  IMPORTED_LOCATION_DEBUG "${math_DBG}"
  IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
)
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe PRIVATE math)

生成的构建系统将在发布配置中构建时链接 myexem.lib,在调试配置中构建时链接 md.lib

导出目标

虽然 IMPORTED 目标本身很有用,但它们仍然要求导入它们的项目知道磁盘上目标文件的位置。IMPORTED 目标的真正强大之处在于,提供目标文件的项目也提供了一个 CMake 文件来帮助导入它们。可以设置一个项目来生成必要的信息,以便其他 CMake 项目可以轻松使用它,无论是从构建目录、本地安装还是打包后。

在其余部分,我们将逐步介绍一组示例项目。第一个项目将创建并安装一个库以及相应的 CMake 配置和包文件。第二个项目将使用生成的包。

让我们首先看看 Help/guide/importing-exporting/MathFunctions 目录中的 MathFunctions 项目。这里有一个头文件 MathFunctions.h 声明了一个 sqrt 函数

#pragma once

namespace MathFunctions {
double sqrt(double x);
}

以及相应的源文件 MathFunctions.cxx

#include "MathFunctions.h"

#include <cmath>

namespace MathFunctions {
double sqrt(double x)
{
  return std::sqrt(x);
}
}

不必过于担心 C++ 文件的具体细节,它们只是一个简单的例子,可以在许多构建系统上编译和运行。

现在我们可以为 MathFunctions 项目创建一个 CMakeLists.txt 文件。首先指定 cmake_minimum_required() 版本和 project() 名称

cmake_minimum_required(VERSION 3.15)
project(MathFunctions)

# make cache variables for install destinations
include(GNUInstallDirs)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

包含 GNUInstallDirs 模块是为了通过将目录作为缓存变量提供给项目,从而使其能够灵活地安装到不同的平台布局中。

使用 add_library() 命令创建一个名为 MathFunctions 的库

add_library(MathFunctions STATIC MathFunctions.cxx)

然后使用 target_include_directories() 命令指定目标的包含目录

target_include_directories(MathFunctions
                           PUBLIC
                           "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
                           "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)

我们需要告诉 CMake,我们希望根据是构建库还是从安装位置使用它来使用不同的包含目录。如果我们不这样做,当 CMake 创建导出信息时,它将导出一个特定于当前构建目录的路径,并且对其他项目无效。我们可以使用 generator expressions 来指定,如果正在构建库,则包含当前源目录。否则,当安装时,包含 include 目录。有关更多详细信息,请参见创建可重定位包一节。

install(TARGETS)install(EXPORT) 命令协同工作,以安装目标(在我们的例子中是库)和旨在方便地将目标导入另一个 CMake 项目的 CMake 文件。

首先,在 install(TARGETS) 命令中,我们将指定目标、EXPORT 名称以及告诉 CMake 在何处安装目标的目的地。

install(TARGETS MathFunctions
        EXPORT MathFunctionsTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

在这里,EXPORT 选项告诉 CMake 创建一个名为 MathFunctionsTargets 的导出。生成的 IMPORTED 目标设置了适当的属性来定义它们的使用要求,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONS 和其他相关的内置 INTERFACE_ 属性。列在 COMPATIBLE_INTERFACE_STRING 和其他兼容接口属性中的用户定义属性的 INTERFACE 变体也会传播到生成的 IMPORTED 目标。例如,在本例中,IMPORTED 目标将通过 INCLUDES DESTINATION 属性指定的目录来填充其 INTERFACE_INCLUDE_DIRECTORIES 属性。由于给出了相对路径,它被视为相对于 CMAKE_INSTALL_PREFIX

注意,我们尚未要求 CMake 安装导出。

我们不能忘记使用 install(FILES) 命令安装 MathFunctions.h 头文件。头文件应安装到 include 目录,如上方的 target_include_directories() 命令所指定。

install(FILES MathFunctions.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

现在 MathFunctions 库和头文件已安装,我们还需要显式安装 MathFunctionsTargets 导出细节。使用 install(EXPORT) 命令导出 MathFunctionsTargets 中的目标,如 install(TARGETS) 命令所定义。

install(EXPORT MathFunctionsTargets
        FILE MathFunctionsTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

此命令生成 MathFunctionsTargets.cmake 文件,并安排将其安装到 ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions。该文件包含适用于下游使用的代码,用于从安装树中导入安装命令中列出的所有目标。

NAMESPACE 选项会将 MathFunctions:: 前缀添加到目标名称,因为它们被写入导出文件。这种双冒号约定给 CMake 一个提示,即当它被下游项目使用时,该名称是一个 IMPORTED 目标。这样,如果提供它的包没有找到,CMake 可以发出诊断消息。

生成的导出文件包含创建 IMPORTED 库的代码。

# Create imported target MathFunctions::MathFunctions
add_library(MathFunctions::MathFunctions STATIC IMPORTED)

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

这段代码与我们在导入库一节中手动创建的示例非常相似。请注意,${_IMPORT_PREFIX} 是相对于文件位置计算的。

外部项目可以使用 include() 命令加载此文件,并像在其自己的树中构建一样引用安装树中的 MathFunctions 库。例如

1 include(GNUInstallDirs)
2 include(${INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions/MathFunctionTargets.cmake)
3 add_executable(myexe src1.c src2.c)
4 target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

第 2 行加载目标 CMake 文件。虽然我们只导出了一个目标,但此文件可以导入任意数量的目标。它们的位置是相对于文件位置计算的,以便可以轻松移动安装树。第 4 行引用导入的 MathFunctions 库。生成的构建系统将链接到其安装位置的库。

可执行文件也可以使用相同的过程进行导出和导入。

任意数量的目标安装都可以与相同的导出名称关联。导出名称被认为是全局的,因此任何目录都可以贡献目标安装。install(EXPORT) 命令只需调用一次即可安装引用所有目标的单个文件。下面是多个导出如何组合到一个导出文件中的示例,即使它们位于项目的不同子目录中。

# A/CMakeLists.txt
add_executable(myexe src1.c)
install(TARGETS myexe DESTINATION lib/myproj
        EXPORT myproj-targets)

# B/CMakeLists.txt
add_library(foo STATIC foo1.c)
install(TARGETS foo DESTINATION lib EXPORTS myproj-targets)

# Top CMakeLists.txt
add_subdirectory (A)
add_subdirectory (B)
install(EXPORT myproj-targets DESTINATION lib/myproj)

创建包

至此,MathFunctions 项目正在导出其他项目使用所需的目标信息。我们可以通过生成配置文件来使这个项目更易于其他项目使用,以便 CMake find_package() 命令可以找到我们的项目。

首先,我们需要对 CMakeLists.txt 文件进行一些添加。首先,包含 CMakePackageConfigHelpers 模块以访问一些用于创建配置文件的辅助函数。

include(CMakePackageConfigHelpers)

然后我们将创建一个包配置文件和一个包版本文件。

创建包配置文件

使用 CMakePackageConfigHelpers 提供的 configure_package_config_file() 命令生成包配置文件。请注意,此命令应取代普通的 configure_file() 命令。它有助于确保通过避免在安装的配置文件中硬编码路径来确保生成的包是可重定位的。INSTALL_DESTINATION 给定的路径必须是 MathFunctionsConfig.cmake 文件将安装到的目的地。我们将在下一节中检查包配置文件的内容。

configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

使用 install(FILES) 命令安装生成的配置文件。 MathFunctionsConfigVersion.cmakeMathFunctionsConfig.cmake 都安装到相同的位置,完成包的安装。

install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

现在我们需要创建包配置文件本身。在这种情况下,Config.cmake.in 文件非常简单,但足以允许下游使用 IMPORTED 目标。

@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake")

check_required_components(MathFunctions)

文件的第一行只包含字符串 @PACKAGE_INIT@。这在配置文件时展开,并允许使用以 PACKAGE_ 为前缀的可重定位路径。它还提供了 set_and_check()check_required_components() 宏。

check_required_components 辅助宏通过检查所有必需组件的 <Package>_<Component>_FOUND 变量来确保所有请求的非可选组件都已找到。即使包没有任何组件,也应在包配置文件末尾调用此宏。这样,CMake 可以确保下游项目没有指定任何不存在的组件。如果 check_required_components 失败,<Package>_FOUND 变量将设置为 FALSE,并且该包被视为未找到。

set_and_check() 宏应用于配置文件中,而非用于设置目录和文件位置的常规 set() 命令。如果引用的文件或目录不存在,该宏将失败。

如果 MathFunctions 包应提供任何宏,则它们应位于单独的文件中,并安装到与 MathFunctionsConfig.cmake 文件相同的位置,并从那里包含。

包的所有必需依赖项也必须在包配置文件中找到。 假设我们的项目需要 Stats 库。在 CMakeLists 文件中,我们将添加

find_package(Stats 2.6.4 REQUIRED)
target_link_libraries(MathFunctions PUBLIC Stats::Types)

由于 Stats::Types 目标是 MathFunctionsPUBLIC 依赖项,因此下游也必须找到 Stats 包并链接到 Stats::Types 库。应在配置文件中找到 Stats 包以确保这一点。

include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

来自 CMakeFindDependencyMacro 模块的 find_dependency 宏通过传播包是 REQUIRED 还是 QUIET 等来提供帮助。find_dependency 宏还会将 MathFunctions_FOUND 设置为 False,如果未找到依赖项,并附带诊断消息,表明如果没有 Stats 包,则无法使用 MathFunctions 包。

练习:MathFunctions 项目添加一个必需的库。

创建包版本文件

CMakePackageConfigHelpers 模块提供了 write_basic_package_version_file() 命令,用于创建简单的包版本文件。当调用 find_package() 时,CMake 会读取此文件,以确定与请求版本的兼容性,并设置一些特定于版本的变量,例如 <PackageName>_VERSION<PackageName>_VERSION_MAJOR<PackageName>_VERSION_MINOR 等。有关更多详细信息,请参见 cmake-packages 文档。

set(version 3.4.1)

set_property(TARGET MathFunctions PROPERTY VERSION ${version})
set_property(TARGET MathFunctions PROPERTY SOVERSION 3)
set_property(TARGET MathFunctions PROPERTY
  INTERFACE_MathFunctions_MAJOR_VERSION 3)
set_property(TARGET MathFunctions APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING MathFunctions_MAJOR_VERSION
)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${version}"
  COMPATIBILITY AnyNewerVersion
)

在我们的示例中,MathFunctions_MAJOR_VERSION 被定义为 COMPATIBLE_INTERFACE_STRING,这意味着它必须与任何依赖项的依赖项兼容。通过在此版本和 MathFunctions 的下一个版本中设置此自定义用户属性,如果尝试将版本 3 与版本 4 一起使用,cmake 将发出诊断。如果包的不同主要版本设计为不兼容,包可以选择采用这种模式。

从构建树导出目标

通常,项目在被外部项目使用之前会先构建和安装。但是,在某些情况下,希望直接从构建树导出目标。然后,这些目标可以被引用构建树的外部项目使用,而无需涉及安装。 export() 命令用于生成从项目构建树导出目标的文件。

如果希望我们的示例项目也能从构建目录中使用,我们只需在 CMakeLists.txt 中添加以下内容

export(EXPORT MathFunctionsTargets
       FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/MathFunctionsTargets.cmake"
       NAMESPACE MathFunctions::
)

这里我们使用 export() 命令为构建树生成导出目标。在这种情况下,我们将在构建目录的 cmake 子目录中创建一个名为 MathFunctionsTargets.cmake 的文件。生成的文件包含导入目标所需的代码,并且可以由了解项目构建树的外部项目加载。此文件特定于构建树,并且不可重定位

可以创建一个合适的包配置文件和包版本文件来为构建树定义一个包,该包可以在没有安装的情况下使用。构建树的使用者只需确保 CMAKE_PREFIX_PATH 包含构建目录,或者在缓存中将 MathFunctions_DIR 设置为 <build_dir>/MathFunctions

此功能的一个应用示例是在交叉编译时在主机平台上构建可执行文件。包含可执行文件的项目可以在主机平台上构建,然后为另一个平台进行交叉编译的项目可以加载它。

构建和安装包

至此,我们已经为项目生成了一个可重定位的 CMake 配置,该配置可在项目安装后使用。让我们尝试构建 MathFunctions 项目

mkdir MathFunctions_build
cd MathFunctions_build
cmake ../MathFunctions
cmake --build .

在构建目录中,请注意 cmake 子目录中已创建 MathFunctionsTargets.cmake 文件。

现在安装项目

$ cmake --install . --prefix "/home/myuser/installdir"

创建可重定位包

install(EXPORT) 创建的包旨在可重定位,使用相对于包本身位置的路径。它们不得引用在构建包的机器上存在但不会在安装包的机器上存在的文件的绝对路径。

在为 EXPORT 定义目标接口时,请记住包含目录应指定为相对于 CMAKE_INSTALL_PREFIX 的相对路径,但不应显式包含 CMAKE_INSTALL_PREFIX

target_include_directories(tgt INTERFACE
  # Wrong, not relocatable:
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/TgtName>
)

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:include/TgtName>
)

$<INSTALL_PREFIX> 生成器表达式可用作安装前缀的占位符,而不会导致不可重定位的包。这在使用了复杂生成器表达式时是必需的

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/TgtName>
)

这也适用于引用外部依赖项的路径。不建议使用可能包含路径的任何属性(例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_LINK_LIBRARIES)来填充与依赖项相关的路径。例如,此代码可能不适用于可重定位包

target_link_libraries(MathFunctions INTERFACE
  ${Foo_LIBRARIES} ${Bar_LIBRARIES}
  )
target_include_directories(MathFunctions INTERFACE
  "$<INSTALL_INTERFACE:${Foo_INCLUDE_DIRS};${Bar_INCLUDE_DIRS}>"
  )

引用的变量可能包含库的绝对路径和包含目录**(在创建包的机器上找到的)**。这将创建一个包,其中包含硬编码的依赖项路径,不适合重定位。

理想情况下,此类依赖项应通过它们自己的导入目标使用,这些目标具有自己的 IMPORTED_LOCATION 和使用要求属性,例如 INTERFACE_INCLUDE_DIRECTORIES,并进行适当的填充。然后,这些导入目标可以与 target_link_libraries() 命令一起用于 MathFunctions

target_link_libraries(MathFunctions INTERFACE Foo::Foo Bar::Bar)

通过这种方法,包仅通过导入目标的名称来引用其外部依赖项。当消费者使用已安装的包时,消费者将运行适当的 find_package() 命令(通过上面描述的 find_dependency 宏)来查找依赖项,并使用其机器上的适当路径填充导入目标。

使用包配置文件

现在我们准备创建一个项目来使用已安装的 MathFunctions 库。在本节中,我们将使用 Help\guide\importing-exporting\Downstream 中的源代码。在该目录中,有一个名为 main.cc 的源文件,它使用 MathFunctions 库计算给定数字的平方根,然后打印结果

// A simple program that outputs the square root of a number
#include <iostream>
#include <string>

#include "MathFunctions.h"

int main(int argc, char* argv[])
{
  if (argc < 2) {
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  double const inputValue = std::stod(argv[1]);

  // calculate square root
  double const sqrt = MathFunctions::sqrt(inputValue);
  std::cout << "The square root of " << inputValue << " is " << sqrt
            << std::endl;

  return 0;
}

像之前一样,我们将从 CMakeLists.txt 文件中的 cmake_minimum_required()project() 命令开始。对于此项目,我们还将指定 C++ 标准。

cmake_minimum_required(VERSION 3.15)
project(Downstream)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

我们可以使用 find_package() 命令

find_package(MathFunctions 3.4.1 EXACT)

创建可执行文件

add_executable(myexe main.cc)

并链接到 MathFunctions

target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

就这样!现在让我们尝试构建 Downstream 项目。

mkdir Downstream_build
cd Downstream_build
cmake ../Downstream
cmake --build .

CMake 配置期间可能出现警告

CMake Warning at CMakeLists.txt:4 (find_package):
  By not providing "FindMathFunctions.cmake" in CMAKE_MODULE_PATH this
  project has asked CMake to find a package configuration file provided by
  "MathFunctions", but CMake did not find one.

  Could not find a package configuration file provided by "MathFunctions"
  with any of the following names:

    MathFunctionsConfig.cmake
    mathfunctions-config.cmake

  Add the installation prefix of "MathFunctions" to CMAKE_PREFIX_PATH or set
  "MathFunctions_DIR" to a directory containing one of the above files.  If
  "MathFunctions" provides a separate development package or SDK, be sure it
  has been installed.

CMAKE_PREFIX_PATH 设置为之前安装 MathFunctions 的位置,然后重试。确保新创建的可执行文件按预期运行。

添加组件

让我们编辑 MathFunctions 项目以使用组件。本节的源代码可在 Help\guide\importing-exporting\MathFunctionsComponents 中找到。此项目的 CMakeLists 文件添加了两个子目录:AdditionSquareRoot

cmake_minimum_required(VERSION 3.15)
project(MathFunctionsComponents)

# make cache variables for install destinations
include(GNUInstallDirs)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_subdirectory(Addition)
add_subdirectory(SquareRoot)

生成并安装包配置文件和包版本文件

include(CMakePackageConfigHelpers)

# set version
set(version 3.4.1)

# generate the version file for the config file
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${version}"
  COMPATIBILITY AnyNewerVersion
)

# create config file
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)

# install config files
install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

如果在下游使用 find_package() 时指定了 COMPONENTS,它们会列在 <PackageName>_FIND_COMPONENTS 变量中。我们可以使用此变量来验证所有必要的组件目标是否包含在 Config.cmake.in 中。同时,此函数将作为自定义 check_required_components 宏,以确保下游仅尝试使用受支持的组件。

@PACKAGE_INIT@

set(_MathFunctions_supported_components Addition SquareRoot)

foreach(_comp ${MathFunctions_FIND_COMPONENTS})
  if (NOT _comp IN_LIST _MathFunctions_supported_components)
    set(MathFunctions_FOUND False)
    set(MathFunctions_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
  endif()
  include("${CMAKE_CURRENT_LIST_DIR}/MathFunctions${_comp}Targets.cmake")
endforeach()

这里,MathFunctions_NOT_FOUND_MESSAGE 设置为诊断信息,指示包无法找到,因为指定了无效组件。此消息变量可以设置为 _FOUND 变量设置为 False 的任何情况,并将显示给用户。

AdditionSquareRoot 目录类似。让我们看看其中一个 CMakeLists 文件

# create library
add_library(SquareRoot STATIC SquareRoot.cxx)

add_library(MathFunctions::SquareRoot ALIAS SquareRoot)

# add include directories
target_include_directories(SquareRoot
                           PUBLIC
                           "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
                           "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)

# install the target and create export-set
install(TARGETS SquareRoot
        EXPORT SquareRootTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# install header file
install(FILES SquareRoot.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# generate and install export file
install(EXPORT SquareRootTargets
        FILE MathFunctionsSquareRootTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

现在我们可以按照前面章节的描述构建项目。要测试使用此包,我们可以使用 Help\guide\importing-exporting\DownstreamComponents 中的项目。与之前的 Downstream 项目有两个不同之处。首先,我们需要找到包组件。将 find_package 行从

find_package(MathFunctions 3.4.1 EXACT)

更改为

find_package(MathFunctions 3.4 COMPONENTS Addition SquareRoot)

并将 target_link_libraries 行从

target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

更改为

target_link_libraries(myexe PRIVATE MathFunctions::Addition MathFunctions::SquareRoot)

main.cc 中,将 #include MathFunctions.h 替换为


#include "Addition.h"
#include "SquareRoot.h"

最后,使用 Addition

  double const sum = MathFunctions::add(inputValue, inputValue);
  std::cout << inputValue << " + " << inputValue << " = " << sum << std::endl;

构建 Downstream 项目并确认它可以找到并使用包组件。