导入和导出指南

简介

在本指南中,我们将介绍 IMPORTED 目标的概念,并演示如何将 CMake 项目外部的现有可执行文件或库文件导入到 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 目标在磁盘上的位置。可能需要调整该位置以匹配上一步中指定的 <install location>

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)

当在 release 配置下构建时,生成的构建系统会将 myexe 链接到 m.lib,而在 debug 配置下构建时,则链接到 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 目标具有适当设置的属性,以定义其 usage requirements,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONS 以及其他相关的内置 INTERFACE_ 属性。在 COMPATIBLE_INTERFACE_STRING 和其他 Compatible Interface Properties 中列出的用户定义属性的 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 辅助宏通过检查所有必需组件的 <PackageName>_<Component>_FOUND 变量来确保所有请求的非可选组件都已找到。即使包没有组件,也应在包配置文件末尾调用此宏。这样,CMake 可以确保下游项目没有指定任何不存在的组件。如果 check_required_components 失败,则 <PackageName>_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_FOUND 设置为 False(如果依赖项未找到),并附带一个诊断消息,表明 MathFunctions 包在没有 Stats 包的情况下无法使用。

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

创建包版本文件

CMakePackageConfigHelpers 模块提供了 write_basic_package_version_file() 命令,用于创建简单的包版本文件。CMake 在调用 find_package() 时会读取此文件,以确定与请求版本的兼容性,并设置一些版本特定的变量,如 <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 中设置此自定义定义的成员属性,cmake 将在尝试使用版本 3 和版本 4 时发出诊断。如果包的不同主版本被设计为不兼容,则包可以选择采用此类模式。

从构建树导出目标

通常,项目在被外部项目使用之前会被构建和安装。然而,在某些情况下,希望直接从构建树导出目标。然后,外部项目可以通过引用构建树而无需安装来使用这些目标。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 定义目标接口时,请记住,包含目录应指定为相对于 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 targets 来使用,这些目标具有自己的 IMPORTED_LOCATION 和使用要求属性(如 INTERFACE_INCLUDE_DIRECTORIES)正确填充。然后可以使用这些导入的目标与 target_link_libraries() 命令为 MathFunctions 链接。

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

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