导入导出指南

介绍

在本指南中,我们将介绍 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,在调试配置中构建时将链接到 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 和其他 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”文件并安排将其安装到“lib/cmake”。该文件包含适合下游使用的代码,用于从安装树导入安装命令中列出的所有目标。

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(${INSTALL_PREFIX}/lib/cmake/MathFunctionTargets.cmake)
2 add_executable(myexe src1.c src2.c )
3 target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

第 1 行加载目标 CMake 文件。虽然我们只导出了一个目标,但这个文件可以导入任意数量的目标。它们的位置是相对于文件位置计算的,因此可以轻松移动安装树。第 3 行引用导入的“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() 被调用以确定与请求版本的兼容性,并设置一些特定于版本的变量,例如 <PackageName>_VERSION<PackageName>_VERSION_MAJOR` 时,CMake 会读取此文件`、``<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 与版本一起使用,cmake <cmake(1)>` 将发出诊断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>
)

这也适用于引用外部依赖项的路径。不建议使用与依赖项相关的路径填充任何可能包含路径的属性,例如:prop_tgt: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 目标 使用,这些目标具有自己的 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
  const double inputValue = std::stod(argv[1]);

  // calculate square root
  const double 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)

就是这样!现在让我们尝试构建“下游”项目。

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 中的项目。与之前的“下游”项目有两个不同之处。首先,我们需要找到包组件。更改 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 库:

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

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