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 很容易。其构建步骤如下所示:
- git clone https://github.com/ninja-build/ninja.git
- cd ninja && python configure.py –bootstrap
安装最新版本的 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 组合。