导入和导出指南

简介

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

导入目标

IMPORTED 目标用于将 CMake 项目外部的文件转换为项目内部的逻辑目标。通过 add_executable()add_library() 命令的 IMPORTED 选项创建 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)

生成的构建系统在以发布配置构建时将 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() 命令指定目标的 include 目录

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

我们需要告诉 CMake 我们希望根据不同的情况下我们的构建库或从已安装的位置使用库来使用不同的 include 目录。如果不这样做,当 CMake 创建导入信息时,它将导出一个当前构建目录特有的路径,该路径对其他项目无效。我们可以使用 生成器表达式 指定如果我们构建库则包含当前源目录。否则,在安装时包含 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 安装导出。

我们不想忘记使用 MathFunctions.h 头文件和 install(FILES) 命令进行安装。如上文 target_include_directories() 命令所述,头文件应安装到 include 目录中。

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_FOUND 设置为 False,同时会附带一个诊断信息,表明如果不使用 Stats 包,就无法使用 MathFunctions 包。

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

创建包版本文件

CMakePackageConfigHelpers 模块提供 write_basic_package_version_file() 命令,用于创建简单的包版本文件。当调用 find_package() 以确定与请求的版本兼容时,以及要设置一些特定于版本的可变变量,如 <PackageName>_VERSION<PackageName>_VERSION_MAJOR<PackageName>_VERSION_MINOR 等时,此文件将由 CMake 读取。有关更多详细信息,请参见 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定义目标的接口时,请记住,包含目录应指定为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 已适当地填充。然后,这些导入的目标可与 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
  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)

就是这样!现在让我们尝试构建 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

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

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