【学习笔记】Mastering CMake (二十)—— 导入导出指南

前言:

学习笔记,随时更新。如有谬误,欢迎指正。


说明:

  1. 红色字体为较为重要部分。
  2. 绿色字体为个人理解部分。

20 导入导出指南

20.1 介绍

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

20.2 导入目标

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

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

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

20.2.1 导入可执行文件

首先,我们将浏览一个简单的示例,该示例创建一个 IMPPRTED 可执行目标,然后用 add_custom_command 命令引用它。

我们需要做一些准备工作才能开始。我们想要创建一个可执行程序,当运行时在当前目录下创建一个基本的 main.cc 文件。这个项目的细节并不重要。导航到 Help/guide/import-export/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/import-export/imports 中找到。在 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)

20.2.2 导入库

以类似的方式,可以通过 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)

生成的构建系统在 Release 配置中构建时将 myexe 链接到 m.lib ,而在 Debug 配置中构建时将链接到 md.lib 。

20.3 导出目标

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

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

让我们从 Help/guide/import-export/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)

# 指定 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

使用 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:include>"
)

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

install(TARGETS)install(EXPORT) 命令一起工作来安装目标(在我们的例子中是一个库)和一个 CMake 文件,目的是为了方便将目标导入到另一个 CMake 项目中。

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

install(TARGETS MathFunctions
        EXPORT MathFunctionsTargets
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib
        RUNTIME DESTINATION bin
        INCLUDES DESTINATION include
)

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

注意,我们还没有要求 CMake 安装导出。

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

install(FILES MathFunctions.h DESTINATION include)

既然已经安装了 MathFunctions 库和头文件,我们还需要显式地安装 MathFunctionsTargets 导出详细信息。使用 install(EXPORT) 命令导出由 install(targets) 命令定义的 MathFunctionsTargets 中的目标。

install(EXPORT MathFunctionsTargets
        FILE MathFunctionsTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION lib/cmake/MathFunctions
)

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

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

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

# 创建被导入目标 MathFunctions::MathFunctions
add_library(MathFunctions::MathFunctions STATIC IMPORTED)

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

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

外部项目可以使用 include 命令加载这个文件,并从安装树中引用 MathFunctions 库,就好像它是在自己的树中构建的一样。例如:

 include(${INSTALL_PREFIX}/lib/cmake/MathFunctionTargets.cmake)
 add_executable(myexe src1.c src2.c )
 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)

20.3.1 创建包

此时,MathFunctions 项目正在导出其他项目需要使用的目标信息。我们可以通过生成一个配置文件使这个项目更容易被其他项目使用,这样 CMake 的 find_package 命令就可以找到我们的项目。

首先,我们需要在 CMakeLists.txt 文件中添加一些内容。首先,包含 CMakePackageConfigHelpers 模块来访问一些用于创建配置文件的辅助函数。

include(CMakePackageConfigHelpers)

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

20.3.1.1 创建包配置文件

使用 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 lib/cmake/MathFunctions
)

使用 install(FILES) 命令安装生成的配置文件。 MathFunctionsConfigVersion.cmake 和 MathFunctionsConfig.cmake 安装到相同的位置,包就(制作)完成了。

install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION lib/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 目标是 MathFunctions 的 PUBLIC 依赖项,因此下游还必须找到 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 项目添加所需的库。

20.3.1.2 创建包版本文件

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
)

# 生成配置文件的版本文件
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 将发出诊断。如果包的不同主要版本被设计为不兼容,包可以选择使用这种模式。

20.3.2 从构建树中导出目标

通常,项目是在外部项目使用之前构建和安装的。然而,在某些情况下,直接从构建树中导出目标是可取的。然后,外部项目可以使用目标,该项目引用构建树,而不涉及安装。 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 。

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

20.3.3 构建和安装一个包

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

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

在构建目录中,观察到文件 MathFunctionsTargets.cmake 已在 cmake 子目录下被创建出来。

现在安装项目:

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

20.4 创建可重新定位的包

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 目标 来使用,这些目标有它们自己的 IMPORTED_LOCATION 和使用需求属性,比如适当填充的 INTERFACE_INCLUDE_DIRECTORIES 。这些导入的目标可能后来被用于对 MathFunctions 对调用的 target_link_libraries 命令:

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

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

20.5 使用包配置文件

现在我们准备创建一个项目来使用安装的 MathFunctions 库。在本节中,我们将使用 Help\guide\import -export\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_requiredproject 命令开始。对于这个项目,我们还将指定 C++ 标准。

cmake_minimum_required(VERSION 3.15)
project(Downstream)

# 指定 C++ 标准
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 的位置,然后再试一次。确保新创建的可执行文件按预期运行。

20.6 添加组件

让我们编辑 MathFunctions 项目来使用组件。本节的源代码可以在 Help\guide\import-export\MathFunctionsComponents 中找到。这个项目的 CMakeLists 文件添加了两个子目录: Addition 和 SquareRoot 。

cmake_minimum_required(VERSION 3.15)
project(MathFunctionsComponents)

# 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 3.4.1)

# 生成配置文件的版本文件
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  VERSION "${version}"
  COMPATIBILITY AnyNewerVersion
)

# 创建配置文件
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION lib/cmake/MathFunctions
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)

# 安装配置文件
install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
        DESTINATION lib/cmake/MathFunctions
)

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

@PACKAGE_INIT@

set(_supported_components Addition SquareRoot)

foreach(_comp ${MathFunctions_FIND_COMPONENTS})
  if (NOT _comp IN_LIST _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 的任何情况下都可以设置此消息变量,并将其显示给用户。

Addition 目录和 SquareRoot 目录类似。让我们看看其中一个 CMakeLists 文件:

# 创建库
add_library(SquareRoot STATIC SquareRoot.cxx)

add_library(MathFunctions::SquareRoot ALIAS SquareRoot)

# 添加包含路径
target_include_directories(SquareRoot
                           PUBLIC
                           "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
                           "$<INSTALL_INTERFACE:include>"
)

# 安装目标并创建导出集合
install(TARGETS SquareRoot
        EXPORT SquareRootTargets
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib
        RUNTIME DESTINATION bin
        INCLUDES DESTINATION include
)

# 安装头文件
install(FILES SquareRoot.h DESTINATION include)

# 生成和安装导出文件
install(EXPORT SquareRootTargets
        FILE MathFunctionsSquareRootTargets.cmake
        NAMESPACE MathFunctions::
        DESTINATION lib/cmake/MathFunctions
)

现在我们可以按照前面章节的描述构建项目了。要使用这个包进行测试,我们可以使用 Help\guide\imports-exports\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 库:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值