CMake 基础系列 - 第 2 部分

Tom Hulton-Harrop | October 19, 2022

如何在 CMake 项目中快速集成第三方库。

动机

C++ 中的依赖关系管理一直是个难题。在没有任何软件包管理器的情况下,手动引入一个库的步骤既耗时又容易出错。幸运的是,CMake 最近(3.11-3.14)增加了一个功能,使这一工作变得异常简单。

示例

让依赖关系管理变得轻而易举的杀手级功能是FetchContent。让我们继续上一部分的内容,为 “Hello, World!”应用程序添加一些有用的功能。假设我们想使用一个迭代器库,但又不想把它复制到源代码树中,然后自己手动配置构建。我们可以这样使用 FetchContent(新代码遵循 Part 1 中的 project 命令)。

...
include(FetchContent) #1
FetchContent_Declare( #2  
  EasyIterator #3
  GIT_REPOSITORY https://github.com/TheLartians/EasyIterator.git #4  
  GIT_TAG aab0c0d8fd17708c64522408d9b304729dbc3a3f #5
)
FetchContent_MakeAvailable(EasyIterator) #6
...
target_link_libraries(${PROJECT_NAME} PRIVATE EasyIterator) #7
...

首先,我们需要将 CMake FetchContent 模块引入我们的 CMakeLists.txt 文件 (#1)。

然后,我们使用 FetchContent_Declare (#2)声明我们要使用的库。

我们给依赖库取一个名字 (#3),并将其指向我们要使用的库的 git 仓库 (#4)(GIT_REPOSITORY 只是众多选项之一,如本地路径的 SOURCE_DIR)。

最后,将库的版本固定在特定的提交(或标签,如果存在的话)(#5)上是个不错的做法,这样后续的上游更改就不会立即破坏我们的库。

FetchContent_MakeAvailable (#6) 然后在幕后进行大量工作,但本质上只是让我们的应用程序可以使用目标。

我们需要做的最后一件事就是链接依赖项(#7)。这样做的好处是,只要我们所依赖的项目设置正确,就不需要接触 include 或库路径,一切都由上游处理,与我们无关。

这样,我们就可以编写一个改进的 ‘Hello, <planet>‘程序,在每次迭代时输出索引。

#include <easy_iterator.h>
...
std::vector<std::string> planets = {    
    "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto?"};
for (auto [index, planet] : easy_iterator::enumerate(planets)) {    
    std::cout << index << ": Hello," << ' ' << planet << '\n';
}

// outputs:
// 1: Hello, Mercury
// 2: Hello, Venus
// 3: Hello, Earth
// ...
//

讨论

FetchContent 对于小型、简单、快速构建的库来说是非常好的。依赖库会直接与我们的应用程序一起构建(依赖库实际上在 build/_deps 中)。(对于较大的依赖库,我们可能需要在较早的阶段单独构建它们,然后使用 find_package 命令将其引入(下次再详述……)。FetchContent 也只适用于使用 CMake 构建的项目,不过现在绝大多数项目都使用 CMake 构建,即使那些不使用 CMake 构建的项目也通常有社区维护的 CMake 版本。以 bgfx.cmake 为例。

进一步阅读

CMake FetchContent docs 是获取更多信息的好地方。

还有一个名为 ‘CMake 傻瓜教程’ 的 CMake 入门视频。(视频质量不是很好,但它很好地介绍了 CMake 的基本使用方法_)。

免责声明:本文仅代表作者个人观点,不代表Open 3D基金会、Open 3D Engine或作者所在公司的观点。

查看该系列的其他部分:

返回博客