导入和导出指南

简介

在本指南中,我们将介绍 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)

生成的构建系统将在发布配置中构建时将 myexe 链接到 m.lib,在调试配置中构建时将 myexe 链接到 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 目标的 INTERFACE_INCLUDE_DIRECTORIES 属性将填充由 INCLUDES DESTINATION 属性指定的目录。由于给出了相对路径,因此它被视为相对于 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)

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

创建包配置文件

使用 configure_package_config_file() 命令,该命令由 CMakePackageConfigHelpers 提供,以生成包配置文件。请注意,应使用此命令代替普通的 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 宏通过传播包是否为 REQUIREDQUIET 等信息来提供帮助。find_dependency 宏还会设置 MathFunctions_FOUNDFalse,如果未找到依赖项,还会提供诊断信息,指出在没有 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 .

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

现在安装项目

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

创建可重定位的包

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

在为 EXPORT 定义目标的接口时,请记住,include 目录应指定为相对于 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}>"
  )

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

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

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

使用这种方法,包仅通过 IMPORTED 目标 的名称来引用其外部依赖项。当使用者使用已安装的包时,使用者将运行适当的 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 项目,并确认它可以找到并使用包组件。