4、构建系统基础概念:Make、Ninja、Soong、Blueprint、Kati的职责与关系

AOSP(Android Open Source Project)的构建系统是Android开发中复杂度最高的基础设施之一。许多开发者初次接触AOSP时,面对make、m、mm、soong_ui、blueprint等命令和术语,往往感到困惑。本章将系统梳理AOSP构建系统中五个核心组件——Make、Ninja、Soong、Blueprint、Kati——各自的职责边界、协作关系以及它们在一次完整构建中的执行顺序。理解这些基础概念,是后续学习模块化构建、增量编译优化、自定义产品配置的基石。

AOSP构建系统核心组件协作关系 Soong_UI (构建入口) soong_ui.bash / 解析命令行参数 Kati (Make兼容层) 解析 Android.mk / Makefile 生成 build.ninja 片段 Blueprint (配置解析) 解析 Android.bp 文件 生成模块定义中间表示 Soong (构建核心引擎) 接收Blueprint IR + Kati输出 生成最终 build.ninja 主文件 Ninja (最终执行器) 读取 build.ninja,并行执行构建任务 输出: system.img / vendor.img / boot.img ... 增量构建反馈 图例 Kati Blueprint Soong Ninja 反馈/增量

上图展示了AOSP构建系统的整体架构。从顶层到底层,构建请求依次经过Soong_UI(入口)、Kati与Blueprint(并行解析)、Soong(核心生成)、Ninja(最终执行)四个阶段。下面逐一剖析每个组件的职责。

4.1 核心原理:五组件各司其职

4.1.1 Make:历史包袱与兼容层

Make是Unix世界最古老的构建工具之一(1976年由Stuart Feldman发明)。AOSP早期版本(Android 7.0之前)完全基于Make构建,通过Android.mk文件描述模块编译规则。Make的优点是灵活、生态成熟,但缺点是:

  • 解析速度慢:AOSP包含数千个Android.mk,Make需要递归解析所有文件,冷启动耗时可达数分钟。
  • 全局变量污染:所有模块共享全局变量,模块间隐式依赖难以追踪。
  • 并行效率低:Make的依赖图构建算法(基于文件时间戳)在大规模项目中容易产生错误或重复执行。

因此,从Android 7.0开始,Google引入了Kati作为Make的“翻译器”,但保留了Make语法兼容性。如今,Make本身已不直接参与构建执行,而是通过Kati间接工作。

4.1.2 Kati:Make到Ninja的翻译器

Kati是一个用C++编写的工具,其核心功能是:读取所有Android.mk文件,解析Make语法,生成等价的build.ninja片段。它解决了Make解析慢的问题,因为Kati会缓存解析结果(.kati_stamp文件),增量构建时只需重新解析变更的Android.mk。

Kati的关键设计决策:

  • 支持大部分Make语法(包括ifeq、foreach、call等),但不支持所有GNU Make扩展。
  • 将Make的“递归展开”模型转换为Ninja的“扁平依赖图”模型。
  • 输出文件为build.ninja的一部分,通常位于out/build-<product>.ninja。

4.1.3 Blueprint:Android.bp的解析器

Blueprint是Google为AOSP设计的声明式构建配置解析器。它读取Android.bp文件(一种类似JSON的格式),将其转换为Soong可理解的模块定义中间表示(IR)。Blueprint本身不执行构建,只负责“读配置”。

与Make的“过程式”写法不同,Blueprint采用“声明式”风格:

// 示例:Android.bp
cc_binary {
    name: "hello_world",
    srcs: ["main.cpp"],
    shared_libs: ["liblog"],
    cflags: ["-Wall"],
}

Blueprint的优势在于:语法简洁、无全局变量、模块间依赖显式声明(通过deps字段),便于静态分析和并行解析。

4.1.4 Soong:构建核心引擎

Soong是AOSP构建系统的“大脑”,负责:

  1. 接收Blueprint的IR:将Android.bp中的模块定义转换为具体的构建规则。
  2. 合并Kati的输出:将Android.mk生成的Ninja片段与Blueprint生成的规则统一。
  3. 生成最终build.ninja:包含所有模块的编译、链接、打包规则。
  4. 管理全局配置:如BoardConfig.mk、ProductConfig.mk等。

Soong使用Go语言编写,支持并发解析和缓存,是AOSP构建性能优化的关键组件。

4.1.5 Ninja:最终执行器

Ninja是一个极简的构建执行工具,设计目标是“快”。它不解析任何配置文件,只读取Soong生成的build.ninja,然后根据文件时间戳和依赖关系并行执行命令。Ninja的特点:

  • 无语法糖:只支持rule、build、default等基本指令。
  • 并行度极高:默认使用所有CPU核心,通过-j参数控制。
  • 增量构建精准:基于文件内容哈希(而非时间戳)判断是否需要重建。

4.2 判断方法与操作流程:一次完整构建的执行链路

当开发者执行source build/envsetup.sh && lunch <product> && make -j16时,实际发生以下流程:

步骤组件输入输出耗时(冷启动)
1Soong_UI命令行参数、环境变量构建配置(产品、变体)<1s
2Kati所有Android.mkbuild.ninja片段(Make部分)30-90s
3Blueprint所有Android.bp模块IR10-20s
4SoongKati输出 + Blueprint IR最终build.ninja5-10s
5Ninjabuild.ninja编译产物(.o、.img等)数分钟至数小时

关键判断点:

  • 如果修改了Android.bp,只需重新运行Blueprint+Soong(步骤3-4),Kati可跳过。
  • 如果修改了Android.mk,则必须重新运行Kati(步骤2)。
  • 如果只修改了C++源码,则直接进入Ninja执行阶段(步骤5),前四步全部跳过。

4.3 案例拆解:一次增量编译的排障实战

场景:某团队在AOSP中添加了一个新的系统服务模块,使用Android.bp定义。首次编译成功,但修改源码后执行make systemimage,发现改动未生效。

初始现象:out/target/product/<device>/system/framework/下的jar包时间戳未更新。

分析过程:

  1. 检查build.ninja中该模块的规则是否存在:grep "my_service" out/build-<product>.ninja —— 规则存在。
  2. 检查Ninja是否认为模块需要重建:ninja -t query out/target/product/<device>/system/framework/my_service.jar —— 输出显示“no work to do”。
  3. 怀疑是Blueprint缓存问题:删除out/soong/build.ninja和out/soong/Android.bp缓存,重新执行make。
  4. 问题依旧。最终发现Android.bp中srcs字段写的是["src/*.cpp"],但新增的源文件放在src2/目录下,未被通配符匹配。

解决方案:修改Android.bp,将srcs改为["src/*.cpp", "src2/*.cpp"],重新执行make。

复盘经验:

  • Blueprint的srcs通配符不会自动递归子目录,需显式声明。
  • 当修改Android.bp后,务必确认Soong重新生成了build.ninja(可通过ninja -t targets验证)。
  • 增量构建失效时,优先检查依赖声明,而非盲目清理输出目录。

4.4 常见误区与总结复盘

误区1:认为Make已完全废弃。实际上,AOSP中仍有大量Android.mk文件(尤其是vendor和硬件相关模块),Kati必须持续维护Make兼容性。

误区2:混淆Blueprint与Soong。Blueprint只负责“读配置”,Soong负责“生成规则”。修改Android.bp后,必须重新运行Soong(通常由make自动触发)。

误区3:手动编辑build.ninja。该文件是自动生成的,手动修改会在下次构建时被覆盖。正确做法是修改Android.bp或Android.mk。

误区4:忽略Kati缓存。Kati的缓存(out/.kati目录)可显著加速增量构建。若缓存损坏,可删除后重新生成,但不要手动修改缓存文件。

总结:AOSP构建系统是一个“分层翻译”架构——Make语法(Kati)和Blueprint声明(Blueprint)被翻译为统一的Ninja规则(Soong),最终由Ninja高效执行。理解这五者的关系,是优化构建速度、排查编译问题的前提。建议开发者熟练掌握以下命令:

# 查看构建配置
m -j16 showcommands
# 查看Ninja依赖图
ninja -t graph out/target/product/<device>/system/bin/app_process
# 清理Soong缓存(不清理输出)
rm -rf out/soong
# 强制重新生成build.ninja
m -j16 nothing