Skip to content

Ninja构建系统

解释 Soong/Kati 到 Ninja 图的生成流程、combined Ninja、执行器选择、增量判断和诊断工具。

基于android-17.0.0_r1
AndroidAOSPNinjaSoong

Ninja构建系统 ​

m、mm 和 mmm 只负责选择初始目标。真正决定动作依赖、并行顺序和增量重用的,是 Soong/Kati 生成的 Ninja 兼容构建图,以及 Soong UI 选择的执行器。

Android 17 的流程可以分成五步:

  1. lunch 提供 Product、Release、Variant;
  2. Soong 解析 Android.bp;
  3. Kati 解析仍保留的 Android.mk;
  4. Soong UI 组合各 Ninja 子图;
  5. Ninja、Siso、N2 或 Ninjago 执行动作图。

m-mm-mmm编译命令 已说明命令如何选择目标。本文从目标进入 Soong UI 后开始,重点说明生成文件、依赖图、增量判断和诊断入口。

本文面向已经会选择构建目标、但还不能解释“为什么重编”或“为什么没有重编”的读者。文章边界 是 Android 17 的 Soong/Kati 生成、combined Ninja 和执行器消费关系,不展开 Blueprint 模块解析 或远程构建服务内部。源码主线从 Build、createCombinedBuildNinjaFile、runNinjaForBuild 到具体 action;读者应能定位输入、输出、depfile、环境过滤和 executor 的所有者,并知道增量结论 何时只能停留在构建图层。

本文以前置的 m-mm-mmm编译命令 为入口;读者若要观察图生成后的 产物如何进入设备,可继续阅读 模块化编译。

1. 生成执行 ​

1.1 Soong/Kati ​

组件主要输入主要输出
Product configlunch 三元组、产品配置、release flags完整构建变量
SoongAndroid.bp、module type、defaultsSoong Ninja 文件
KatiAndroid.mk 与 Make 兼容配置Kati Ninja 文件
Soong UISoong/Kati 输出Combined Ninja 文件
ExecutorCombined DAG对象、库、APK、APEX 和镜像

Ninja 不理解 Java、C++、AIDL 或 APK。生成器已经把这些语义转换成“输入、输出、命令和依赖”。

1.2 构建入口 ​

Config.SoongNinjaFile 根据当前 product 选择文件名:

源码文件:build/soong/ui/build/config.go

相关函数/类型:SoongNinjaFile

go
func (c *configImpl) SoongNinjaFile() string {
    targetProduct, err := c.TargetProductOrErr()
    if err != nil {
        return filepath.Join(c.SoongOutDir(), "build.ninja")
    }

    // 说明:正常产品构建把product写入文件名,
    // 不同产品不会共享同一份Soong动作图。
    return filepath.Join(
        c.SoongOutDir(),
        "build."+targetProduct+c.CoverageSuffix()+".ninja",
    )
}

Soong 还会产生 phony、dist 和 nodist 辅助文件。它们都是生成物,不应手工编辑。

1.3 KatiNinja文件 ​

Kati 为 Make 兼容部分生成主构建和 packaging Ninja 文件。文件名带 Kati suffix,用于区分不同 产品与配置组合。

只要产品仍依赖 Android.mk,最终图就可能同时包含 Soong 与 Kati 输出。迁移到 Android.bp 不表示 整个源码树已经完全移除 Make。

2. Ninja文件 ​

2.1 Combined职责 ​

Soong UI 创建一份 combined 文件,统一设置 builddir 与资源 pool,并通过 subninja 引入:

  • Kati build Ninja;
  • Kati package Ninja;
  • Soong Ninja。

执行器只需要使用 combined 文件作为入口,就能看到完整构建 DAG。

2.2 Combined文件 ​

源码文件:build/soong/ui/build/config.go

相关函数/类型:CombinedNinjaFile

go
func (c *configImpl) CombinedNinjaFile() string {
    if c.katiSuffix == "" {
        return filepath.Join(c.OutDir(), "combined.ninja")
    }

    // 说明:Kati suffix把产品相关配置编码进文件名。
    return filepath.Join(
        c.OutDir(),
        "combined"+c.KatiSuffix()+".ninja",
    )
}

诊断时不要猜文件名。应从构建日志、Soong config 或 showcommands 确认当前 combined 文件。

3. Ninja语义 ​

3.1 基本元素 ​

下面是概念示例,不是 AOSP 生成文件原文:

text
rule compile_cpp
  command = clang -c INPUT -o OUTPUT
  depfile = OUTPUT.d
  deps = gcc

build out/obj/example.o: compile_cpp src/example.cpp
build example: phony out/obj/example.o
default example
元素作用
rule定义动作模板
build定义输入、输出和依赖
depfile补充编译器发现的头文件依赖
pool限制高成本动作并发
phony聚合逻辑目标
default未指定目标时的入口
subninja引入其他 Ninja 文件

3.2 DAG调度 ​

无依赖关系的节点可以并行;依赖链上的动作必须等待前置输出完成。

4. Android17执行器 ​

4.1 执行器选择 ​

当前 config 将默认执行器设为 Siso,并允许 SOONG_NINJA 选择 Ninja、N2、Siso 或 Ninjago:

源码文件:build/soong/ui/build/config.go

go
var NINJA_DEFAULT ninjaCommandType = NINJA_SISO

switch os.Getenv("SOONG_NINJA") {
case "ninja":
    ret.ninjaCommand = NINJA_NINJA
case "n2":
    ret.ninjaCommand = NINJA_N2
case "siso":
    ret.ninjaCommand = NINJA_SISO
case "ninjago":
    ret.ninjaCommand = NINJA_NINJAGO
default:
    ret.ninjaCommand = NINJA_DEFAULT
}

// 说明:当前Darwin环境在默认Siso时回退到Ninja。
if runtime.GOOS == "darwin" &&
   ret.ninjaCommand == NINJA_SISO {
    ret.ninjaCommand = NINJA_NINJA
}

因此,“Android 使用 Ninja”更准确地表示 Android 使用 Ninja 兼容动作图。实际执行器由平台和配置 决定。

4.2 规则边界 ​

Soong UI 会把 duplicate build、missing depfile 和 missing output 等问题提升为错误。模块规则应完整 声明输入和输出,不能依赖某个执行器恰好忽略错误。

5. Soong构建 ​

5.1 基础命令参数 ​

源码文件:build/soong/ui/build/ninja.go

相关函数/类型:runNinja

go
func runNinja(ctx Context, config Config, ninjaArgs []string) {
    // ...

    executable = config.NinjaBin()
    args = []string{
        "-d", "keepdepfile",
        "-d", "keeprsp",
        "-d", "stats",
        "--frontend_file", fifo,
        "-w", "dupbuild=err",
        "-w", "missingdepfile=err",
        "-j", strconv.Itoa(parallel),
    }

    args = append(args, ninjaArgs...)

    if config.keepGoing != 1 {
        args = append(args, "-k", strconv.Itoa(config.keepGoing))
    }

    // 说明:combined文件作为完整图入口。
    args = append(args, "-f", config.CombinedNinjaFile())

    cmd := Command(ctx, config, e, "ninja", executable, args...)
    // ...
}

不同 executor 的启动参数不同,但都从 config 取得并行度、目标和 combined 图。

5.2 额外Ninja参数 ​

Soong UI 同时读取 NINJA_ARGS 和 NINJA_EXTRA_ARGS:

bash
NINJA_ARGS="-d explain" m SystemUI
NINJA_ARGS="-n" m SystemUI

-d explain 解释 dirty 原因;-n 只模拟动作。不要把诊断变量长期写入 shell profile。

6. 增量构建依据 ​

6.1 输入输出 ​

生成器必须把真实输入写入 Ninja 图。C/C++ 动作通常通过 depfile 补充头文件依赖。遗漏依赖可能导致 修改后不重编;不稳定依赖则会导致每次重编。

6.2 命令行变化 ​

编译器、宏、flags 和输出路径属于动作身份。会影响输出的配置必须进入命令或显式输入。

6.3 环境变量 ​

Ninja 不会因为任意环境变量变化自动重建。Soong UI 因此限制执行环境,并把可见变量写入 ninja.environment。真正影响输出的变量应由生成器复制到命令或依赖文件。

6.4 日志 ​

.ninja_log 与 deps log 用于增量和诊断。它们是派生状态,不是源码事实,也不应在不了解影响时随意 删除。

7. 诊断工具 ​

7.1 常用subtools ​

命令作用
-t targets列出目标
-t commands列出重建目标所需命令
-t query查询直接输入和输出
-t inputs递归列出输入
-t deps查看 deps log
-t graph输出 Graphviz DAG
-t path / paths查询依赖路径
-t compdb导出 compilation database
-t ninja_files列出递归载入文件

通常通过 m 加 NINJA_ARGS 使用诊断选项,避免绕过 Soong UI 环境。直接调用 Ninja 时必须指定正确 combined 文件。

7.2 showcommands ​

showcommands 使用 Ninja -t commands 输出目标命令。需要重新生成构建图时,可通过 NINJA_ARGS="-t commands ..." m 进入正常 Soong UI 流程。

8. 常见问题 ​

8.1 重复构建 ​

使用 -d explain 查看 dirty 原因。常见原因是生成文件时间戳变化、命令变化、depfile 不稳定、环境 状态变化或输出缺失。

8.2 问题定位 ​

检查 query、inputs 和 deps。真实输入没有进入图时,应修复 Android.bp/Android.mk 或生成器,而不是 靠删除 out 目录维持正确性。

8.3 Ninja编辑风险 ​

生成物会被覆盖。修改应落在模块描述、产品配置或生成器。

8.4 日志显示Siso ​

Siso 是执行器选择,不表示 Ninja 图消失。诊断参数是否支持要查看对应 executor。

9. 增量构建图 ​

遇到“为什么重编”时,从 -d explain 的第一条 dirty 原因向输入方向回溯;遇到“为什么没重编”时, 从目标的 query、inputs 和 depfile 向真实源码输入核对。两类问题方向相反,但都要落到同一组图事实: 显式输入、隐式输入、命令身份、输出和 consumer。

Soong UI 的配置与环境测试约束了 executor 参数、受控环境和辅助目标的生成逻辑;产品构建则在这些 契约上继续加入实际源码、host tools、lunch 配置与模块依赖。诊断时应先确认当前 combined 文件和 executor,再决定问题属于模块描述、生成器还是执行 action。

一个健康的增量图应同时满足:真实输入变化会使正确 action 变 dirty,无关变化不会扩大重编范围, 缺失输出会被重新生成,命令或关键配置变化会进入动作身份。依靠手工删除 out 才能维持这些行为, 说明图的输入输出契约仍有缺口。

10. 构建图测试 ​

第一组验证在固定目标上修改一个真实 Android.bp 输入,运行 ninja -d explain,断言包含该输入的 action 变 dirty,而与它无依赖的目标不被牵连;这证明 depfile/DAG 的局部传播,不证明产品全量构建 正确。第二组只改变一个被 Soong UI 允许传递的关键环境变量,比较 combined Ninja 中 action command 和 dirty 原因,断言命令身份变化会触发重建;这证明环境过滤与动作身份的边界,不证明任意环境变量 都会生效。两组结果都应保存目标、输入、输出和 executor 名称,避免把 Siso 或 Ninja 的差异误认为 图生成差异。