Ninja 代码结构分析

本文回答两个问题:源码怎么组织,以及 Ninja 怎么构建它自己(含自举 bootstrap 与 re2c 代码生成)。建议先读 架构概述 建立全局印象。

1. 仓库顶层

ninja/
├── src/               几乎全部源码(~30700 行 C++,含测试)
├── doc/               manual.asciidoc(官方手册)+ doxygen
├── misc/              ninja_syntax.py、bash/zsh 补全、工具脚本
├── windows/           Windows 资源文件
├── configure.py       Python 生成器:产出 build.ninja(可自举)
├── CMakeLists.txt     CMake 构建(与 configure.py 二选一)
├── README.md          构建说明
└── CONTRIBUTING.md / RELEASING.md / COPYING(Apache-2.0)

整个工具的代码量很小——约 3 万行(含全部单元测试与性能基准)。这种”小”本身就是设计的一部分:手册的非目标里明确拒绝内置规则、构建期条件判断等会让代码膨胀、让运行变慢的特性。

2. src/ 按职责分组

src/ 里文件很多(127 个),但按职责能干净地分成几组。注意一个命名约定:每个模块 foo.{h,cc} 通常都配一个 foo_test.cc 单元测试;带 -win32/-posix 后缀的是平台特定实现;.in.cc 是 re2c 的输入(见 §4)。

构建图模型 / 全局状态

解析 / 词法

构建 / 执行

日志 / 依赖

子进程 / 平台

工具类

子命令工具(-t ...

3. 入口与 NinjaMain

整个 CLI 都在 ninja.cc 里。main()ninja.cc:1924)是唯一的外部符号,其余几乎都在匿名命名空间内。main 只是个薄封装,真正的驱动是 real_mainninja.cc:1832)。

核心数据结构都挂在 struct NinjaMain : public BuildLogUserninja.cc:90)上,这样各个 -t 工具能直接访问:

State state_;                         // ninja.cc:102  构建图
RealDiskInterface disk_interface_;    // ninja.cc:105  磁盘
BuildLog build_log_;                  // ninja.cc:110  .ninja_log
DepsLog deps_log_;                    // ninja.cc:111  .ninja_deps

子命令通过 ChooseToolninja.cc:1338)查一张静态表 kTools[]。每个 Tool 带一个 when 阶段枚举(RUN_AFTER_FLAGS / RUN_AFTER_LOAD / RUN_AFTER_LOGSninja.cc:210),real_main 在对应阶段调用它——例如 -t graph 需要先加载 manifest,所以是 RUN_AFTER_LOAD。选项解析在 ReadFlagsninja.cc:1730),用 getopt_long,短选项串是 "d:f:j:k:l:nt:vw:C:h"ninja.cc:1746)。

一个”速度优先”的小细节:real_main 顶部注释(ninja.cc:1833)说明它故意用 exit() 而不是 return,”to avoid potentially expensive cleanup when destructing NinjaMain”——退出时干脆不析构那些大数据结构。

4. 构建系统:configure.py、CMake 与 re2c

Ninja 可以用两种方式构建(README.md:22):Python 生成器 configure.py,或 CMake。

4.1 configure.py:生成 build.ninja,并能自举

configure.pymisc/ninja_syntax.pyWriter 生成一份 build.ninjaconfigure.py:250)。它把核心源码编成两个对象库 libninja-re2c.aconfigure.py:535)和 libninja.aconfigure.py:588),再加 ninja.ccmain() 链成 ninjaconfigure.py:601)。

自举(--bootstrap 解决”先有鸡还是先有蛋”:没有 ninja 怎么构建 ninja?class Bootstrapconfigure.py:121)包住 Writer,在生成每条 build 边的同时直接执行对应的编译命令(_run_commandconfigure.py:196),于是不依赖任何已有的 ninja 就能产出一个 ./ninja;之后这个 ./ninja 再用刚写出的 build.ninja 重新构建自己(README.md:31./configure.py --bootstrap)。

configure.py 还会生成一条自我再生成规则configure.py:762):规则 configure 重跑 configure.py,且 build.ninja 依赖 configure.pymisc/ninja_syntax.py。所以你改了生成器脚本,下次 ninja 会先重新生成 build.ninja ——这正是 real_main 重建循环(架构概述 §4)所消费的能力。

4.2 re2c:生成词法器

Ninja 的两个扫描器——manifest 词法器和 depfile 解析器——不是手写的,而是用 re2c.in.cc 里的正则/动作块生成 DFA。configure.py:513-518configure.py:513)定义了 re2c 规则:

n.rule('re2c',
       command='re2c -b -i --no-generation-date --no-version -o $out $in', ...)
n.build(src('depfile_parser.cc'), 're2c', src('depfile_parser.in.cc'))
n.build(src('lexer.cc'), 're2c', src('lexer.in.cc'))

生成出的 .cc 第一行就是 /* Generated by re2c */lexer.cc:1)。重要的是:生成结果已经签入仓库,所以普通构建不需要装 re2c——只有当你修改了 .in.cc 语法时才需要它(configure.py:520 有相应警告)。读语法逻辑请看 .in.cc.cc 只是把 DFA 状态机展开后的等价代码。

4.3 CMake

CMakeLists.txt 是等价的另一条路:同样 find_program(RE2C) 并在缺失时回退到签入的 .ccCMakeLists.txt:63),同样建 libninja-re2c / libninja 两个 OBJECT 库与 ninja 可执行文件(CMakeLists.txt:208),要求 cxx_std_17CMakeLists.txt:192)。

5. 测试与性能基准

测试基于 GoogleTest。入口 ninja_test.cc 只是 InitGoogleTest + RUN_ALL_TESTS(),所有 *_test.cc 链进同一个 ninja_test 二进制。

共享测试设施在 test.h

  • StateTestWithBuiltinRulestest.h:30)——预置了一个 cat 规则的 State fixture。
  • VirtualFileSystem : public DiskInterfacetest.h:51)——内存文件系统 mock,Create()/Tick() 让测试脚本化文件内容与 mtime。这是把构建/图逻辑与真实 IO 解耦的关键缝隙,配合 CommandRunner 接口(build.h:154)可被 mock,使整条构建主循环能在内存里被测。
  • ScopedTempDirScopedFilePathtest.h:90)——RAII 清理真实临时文件。

测试按模块一一对应(最大的 build_test.cc 有约 4400 行,针对 VirtualFileSystem 跑整个 Builder/Plan 循环)。注意:单元测试只有在通过 --gtest-source-dir 提供 GoogleTest 源码时才会被编译(configure.py:621)。另有一组独立的性能基准 *_perftest.cchash_collision_bench.cc,不属于 ninja_test

6. 小结:行号速查

主题 位置
入口 main / real_main ninja.cc:1924 / ninja.cc:1832
NinjaMain 数据结构 ninja.cc:90
子命令表 ChooseTool ninja.cc:1338
选项解析 ReadFlags ninja.cc:1730
自举 Bootstrap configure.py:121
自我再生成规则 configure.py:762
re2c 规则 configure.py:513
生成标记 lexer.cc:1
测试入口 / VFS mock ninja_test.cc:17 / test.h:51

下一篇 构建图模型 进入 Node/Edge/State 的数据结构细节。


本站访问量

This site uses Just the Docs, a documentation theme for Jekyll.