CMake 与Ninja 组合的关键点

Blog
Author:
Dori ExtermanDori Exterman
Published On:
12月 2, 2021
Estimated reading time:
1 minutes

Ninja(忍者)一词让人联想到潜行和速度。在开发“另一个构建系统”时——正如 Ninja 的创造者 Evan Martin 在其手册中提到的那样——速度是最重要的。将构建系统命名为 Ninja 非常恰当,我将在这篇博文中向您展示它的功能,并强调它的独特之处。让我们开始吧。

Ninja 是什么,为什么是另一个构建系统?

让我们从为什么发明 Ninja 开始讨论。在将 Chromium 从 Windows 移植到其他操作系统期间,构建性能成为了一大障碍。点击此处了解全部详情。使用 Makefiles 被认为是次优选择,有一个新的构建系统在概念上非常类似于 Make,但它侧重于速度。它将 Chrome 的构建启动时间缩短到不到一秒,并且很快就实现开源了。它通过以下方式实现了这一壮举:

  • 在设计原则上,侧重速度而非便捷
  • 拥有最少的策略或内置规则集(由 ninja 输入文件处理)
  • 构建始终并行运行,默认基于系统拥有的 CPU 数量

每个构建系统最终都会通过解析构建文件来创建依赖关系图。(在下一节中,我列出了一张 Ninja 本身的图像)。构建过程遍历此图,以获得最终输出。对于 Ninja,这是一个两阶段的遍历,其中:

  • 在第一阶段中,将图形从最终输出向上遍历到输入文件,以查看是否有任何修改,并为构建创建一个计划
  • 在第二阶段中,按照计划将图形从输入文件向下遍历并并行执行

此外,以下低级设计决策有助于 Ninja 提速:

  • Re2c 用于解析构建文件,效率很高
  • Ninja 将构建文件中的路径规范化。它不是将文件路径视为字符串,而是将路径转换为 Node 对象,从而消除用于路径相等性检查的成本昂贵的字符串比较。若要比较两条路径是否相等,Ninja 只需要进行非常快速的指针比较
  • Ninja 以二进制序列化格式保存构建的配置(例如使用的编译标志)。要确定输出是否因构建配置更改而过期,只需要进行二进制哈希比较即可。

所有这些低级优化使 Ninja 快速高效。

获取 Ninja

在 Windows 上,从源代码构建 Ninja 很容易。其构建步骤如下所示:

安装最新版本的 Python,并打开 Visual Studio x64 本机工具命令提示符。发出上述两个命令,其中第一个命令将从其 GitHub 仓库下载 Ninja 源代码,第二个命令将神奇地构建 Ninja。

Ninja 是使用 Ninja 和一种称为引导的技术构建的。引导步骤首先构建一个名为 ninja.bootstrap.exe 的可执行文件和一个 build.ninja 文件。此引导可执行文件进一步用于构建 ninja.exe。构建速度非常快,因为 Ninja 本身的依赖项非常小。我使用命令生成了以下 Ninja 依赖关系图:

  • ninja -t graph ninja.exe > graph_ninja.dot
  • dot -Tpng graph_ninja.dot > graph_ninja.png

 

使用 Ninja

要使用 Ninja 进行软件构建,我们需要创建默认名为 build.ninja 的输入构建文件。下面我们以构建 Ninja 所创建的 build.ninja 部分为例:

# This file is used to build ninja itself.

# It is generated by configure.py.

ninja_required_version = 1.3

# The arguments passed to configure.py, for rerunning it.

configure_args =

root = .

builddir = build

cxx = cl

ar = link

cflags = /showIncludes /nologo /Zi /W4 /WX /wd4530 /wd4100 /wd4706 /wd4244 $

/wd4512 /wd4800 /wd4702 /wd4819 /wd4127 /wd4355 /wd4091 /GR- /wd4267 $

/DNOMINMAX /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 $

/DNINJA_PYTHON=”python.exe” /FS /Ox /DNDEBUG /GL -I.

ldflags = /DEBUG /libpath:$builddir /LTCG /OPT:REF /OPT:ICF

rule cxx

command = $cxx $cflags -c $in /Fo$out /Fd$builddir\$pdb

description = CXX $out

deps = msvc

rule ar

command = lib /nologo /ltcg /out:$out $in

description = LIB $out

rule link

command = $cxx $in $libs /nologo /link $ldflags /out:$out

description = LINK $out

 

手动创建这样一个文件非常麻烦。输入 CMake,它有一个专为 Ninja 设计的后端生成器!

通过 CMake 使用 Ninja

手动创建 Ninja 的输入文件非常难。诸如 CMake 构建生成器系统可用于为 Ninja 创建输入文件。为了展示如何通过 CMake 使用 Ninja,让我们使用 CMake 构建 Ninja,并将 Ninja 作为后端。

从您下载 Ninja 的目录发出以下命令:

cmake -Bbuild-cmake -H. -GNinja

这将创建一个名为 build-cmake 的文件夹,您会在其中找到一个名为 build.ninja 的文件。这是 CMake 生成的 build.ninja 的第一部分

 

# CMAKE generated file: DO NOT EDIT!

# Generated by “Ninja” Generator, CMake Version 3.19

# This file contains all the build statements describing the

# compilation DAG.

# =================================================

# Write statements declared in CMakeLists.txt:

#

# Which is the root file.

# =================================================

# =================================================

# Project: ninja

# Configurations: Debug

# =================================================

#############################################

# Minimal version of Ninja required by this file

ninja_required_version = 1.5

#############################################

# Set configuration variable for custom commands.

CONFIGURATION = Debug

# =================================================

# Include auxiliary files.

#############################################

# Include rules file.

include CMakeFiles\rules.ninja

 

确保路径中包含引导版本创建的 ninja.exe。现在发出以下命令:

  • cd build-cmake && cmake –build .

这将使用带有 CMake Ninja 后端的 Ninja 构建 Ninja。默认情况下,Ninja 会根据系统上可用 CPU 的数量并行运行构建命令。下面我们修改 cmake build 命令,使 Ninja 显示构建统计信息。

  • cmake –build . — -d stats

注:之后的所有参数都由 CMake 传递给底层构建系统。

这给了我们很好的构建统计数据:

Finished generating code

metric                  count   avg (us)        total (ms)

.ninja parse            2       1221.5          2.4

canonicalize str        899     0.0             0.0

canonicalize path       9473    0.0             0.4

lookup node             1563    0.0             0.0

.ninja_log load         1       51.0            0.1

.ninja_deps load        1       32.0            0.0

node stat               443     24.9            11.0

StartEdge               72      62498.4         4499.9

FinishCommand           69      2493.7          172.1

CLParser::Parse         61      1157.5          70.6

path->node hash load 0.72 (367 entries / 512 buckets)

我从之前的博客中复制了一张图片,其中清楚地展示了 Ninja 与 Make 在两个开源项目(bullet3 和 LLVM)的编译时间上的对比情况。

图片来源

另一项研究,它展示了 Ninja 在编译时间方面如何优于 Make。

结论

在我之前的一篇博客中,我曾将 Ninja 描述为一个专注于速度的小型开源构建系统。在这篇博文中,我们(可以从我们使用 ninja -t 命令创建的依赖关系图中)看到了整个 Ninja 源代码有多小,以及设置和使用它有多容易。我们还可以看到 CMake 如何与 Ninja 完美配合,因为 CMake 内置了用以支持 Ninja 的生成器。正是由于这些原因,Visual Studio 2019 将默认生成器作为 Ninja 用于 WSL2 构建。如果您想为您的项目提供一个简洁的构建系统,请考虑使用 CMake 和 Ninja 组合。