系统运维:Ninja 构建工具详解

163 阅读6分钟

Ninja 是一种专注于速度和效率的构建工具,最初由 Google 的工程师 Evan Martin 开发,主要目的是作为一个比传统构建工具(如 Make)更快的替代品。Ninja 本身并不提供高级的构建规则和语法,而是依赖于其他工具生成其构建文件(通常是 .ninja 文件),然后使用 Ninja 工具执行实际的构建任务。Ninja 在构建系统中常与 CMake 一起使用,CMake 会生成 Ninja 的构建文件并将其传递给 Ninja 执行,从而加速构建过程。

Ninja a small build system tool on Linux with a focus on speed.png

本文将介绍 Ninja 构建工具的背景、特点、工作原理及其使用方法。

1. Ninja 的背景

Ninja 的开发始于 2009 年,作为 Google 内部使用的构建工具,用于加速 Android 和 Chromium 项目的构建过程。Ninja 通过将构建任务划分为更小的单元并尽量减少不必要的操作,显著提升了构建速度。它特别适用于大规模项目和多核处理器,能够高效地管理并行化构建任务。

相比于传统的 Make 工具,Ninja 具有以下显著特点:

  • 极快的构建速度:Ninja 将构建过程拆分为细小的操作,并且通过智能的增量构建减少无用的操作,从而显著加速构建速度。
  • 并行构建:Ninja 能够充分利用多核处理器,通过并行化构建任务来提高构建效率。
  • 简洁的构建规则:Ninja 只关注构建的执行部分,规则非常简洁,没有复杂的语法和依赖描述,依赖生成规则(如 CMake)来提供构建描述。

2. Ninja 的工作原理

Ninja 的工作方式可以分为三个主要步骤:

  1. 生成构建描述文件: Ninja 本身并不直接指定构建任务的规则。相反,构建描述文件(.ninja 文件)是由其他工具(如 CMake)生成的。这个文件包含了构建的所有规则和依赖关系。构建描述文件是 Ninja 执行构建时的核心,它提供了任务之间的依赖信息、构建命令以及如何执行任务。

  2. 执行构建操作: Ninja 解析 .ninja 构建描述文件并执行其中定义的任务。Ninja 的任务粒度非常细小,每个任务通常对应一个操作,比如编译一个源文件、链接一个对象文件等。Ninja 会根据这些任务的依赖关系,安排合适的顺序执行。

  3. 增量构建: Ninja 支持增量构建(incremental build)。也就是说,当某个文件发生变化时,Ninja 会仅重建那些受到影响的部分,而不是重新构建整个项目。Ninja 会通过检查文件的时间戳来判断哪些文件需要重建,从而减少不必要的构建操作,提高构建效率。

3. Ninja 与 Make 的比较

Ninja 和 Make 都是构建工具,它们有一些相似之处,但在实现和效率上有很大的区别。以下是两者的一些对比:

特性NinjaMake
构建速度极快,特别适合大规模项目和并行化构建比 Ninja 慢,尤其是在复杂项目中
构建规则简单的构建描述,依赖其他工具生成支持复杂的构建规则和自定义规则
并行化构建自动并行化,基于 CPU 核心数需要手动配置并行化
增量构建高效的增量构建,只构建有变化的部分增量构建性能较差,有时需要手动清理依赖
可扩展性专注于速度,规则和扩展依赖外部工具灵活性高,可通过 Makefile 自定义

从表格中可以看出,Ninja 的构建速度和增量构建能力在大多数情况下都要优于 Make,特别是在处理复杂的项目时。

4. 使用 Ninja 构建

4.1 安装 Ninja

在大多数 Linux 发行版中,Ninja 都可以通过包管理工具安装。例如:

  • UbuntuDebian:

    sudo apt-get install ninja-build
    
  • CentOS:

    sudo yum install ninja-build
    
  • macOS (使用 Homebrew):

    brew install ninja
    

4.2 使用 CMake 生成 Ninja 构建文件

Ninja 本身不生成构建文件,而是依赖于像 CMake 这样的构建生成器来创建 .ninja 构建文件。使用 CMake 时,可以指定 Ninja 作为构建工具,如下所示:

cmake -G Ninja /path/to/your/source

这个命令会生成一个 build.ninja 文件,并将其用于后续的构建操作。-G Ninja 告诉 CMake 使用 Ninja 作为构建工具生成构建文件。

4.3 执行 Ninja 构建

一旦生成了 .ninja 构建文件,可以使用以下命令来执行构建:

ninja

Ninja 会根据 build.ninja 文件中的规则执行构建操作,完成后生成目标文件或应用程序。

4.4 使用 Ninja 进行增量构建

Ninja 的增量构建功能非常强大。当你修改某个源文件时,Ninja 会自动只重新构建受到影响的文件。例如:

ninja my_target

这个命令只会重建与 my_target 相关的文件,而不必重新构建整个项目。Ninja 会根据文件的时间戳来判断哪些文件已发生变化。

4.5 清理构建产物

虽然 Ninja 本身没有类似于 make clean 的命令,但可以使用以下命令删除构建产物:

ninja -t clean

这个命令会删除所有构建的目标文件,但不会删除源代码和其他非构建文件。

5. Ninja 的高级特性

Ninja 提供了一些高级功能,可以帮助开发者更高效地管理构建过程:

5.1 并行构建

Ninja 会自动检测系统的 CPU 核心数并进行并行构建,默认情况下,它会最大化利用可用的 CPU 核心。例如,Ninja 会并行执行多个任务,从而加速构建过程。

可以通过 -j 参数手动指定并行任务的数量:

ninja -j 8

这个命令会并行执行最多 8 个任务。

5.2 自定义构建规则

尽管 Ninja 本身只关注执行部分的构建任务,但它也允许通过 build 语句来定义自定义的构建规则。比如:

build my_target: cc my_source.c

这个规则会指定如何将 my_source.c 编译为 my_target

5.3 持久化构建

Ninja 还支持持久化构建信息,可以通过 ninja -t compdb 命令生成一个完整的构建命令数据库,该数据库可以用于其他工具(如 Clang 等)来获取构建命令和参数。

ninja -t compdb cxx > compile_commands.json

这个命令会生成一个 compile_commands.json 文件,包含了所有编译命令的信息,便于工具链集成。

6. 总结

Ninja 是一个高效、快速的构建工具,特别适用于大规模项目和需要高度并行化的构建任务。它通过简单的构建描述文件和高效的增量构建机制,显著提高了构建速度,尤其在处理复杂的、依赖关系密集的项目时,具有明显的优势。尽管 Ninja 本身并不提供构建规则的编写能力,但它与 CMake 等工具的配合使用,使得它成为现代软件开发中不可或缺的构建工具之一。

如果需要处理一个庞大的项目,尤其是涉及多核 CPU 的情况下,使用 Ninja 作为构建工具能够显著提升构建效率,并帮助我们更快地迭代开发。