第4章 Soong构建系统入门:Android.bp语法、模块定义、build.go核心概念

4.1 Soong构建系统概述

Soong是AOSP从Android 7.0开始引入的下一代构建系统,用Go语言编写,逐步取代传统Android.mk/Makefile体系。其核心思想是:模块描述(Android.bp)→ Blueprint解析 → 构建逻辑(build.go) → Ninja文件 → 最终构建。相比Make,Soong提供更快的增量构建、严格的模块依赖检查、丰富的模块类型和更强的可扩展性。

关键对比:Make体系依赖全局变量隐式传递,Soong强制通过Android.bp显式声明模块属性,避免隐式副作用,使构建更可预测。

4.2 Soong构建流程

理解Soong必须先理解其分层架构:

  • Blueprint解析层:读取所有Android.bp文件,解析为Go结构体,生成模块依赖图。
  • 模块注册层(ModuleFactory):根据模块类型(如cc_library)调用对应的工厂函数构建模块实例。
  • 属性处理层(PropertyStruct):将bp中的JSON-like属性映射到Go结构体的字段,支持类型校验、必要值检查。
  • 构建逻辑层(build.go):每个模块类型通过实现Build方法生成具体规则(编译、链接、打包等),最终输出Ninja构建文件。
  • Ninja执行层:Soong调用Ninja解析生成的*.ninja文件并执行实际编译。
核心命令:构建时AOSP会自动调用soong_build,你通常只需要执行make或直接m,Soong会负责整个转换链。
Soong构建系统核心流程 Soong构建系统核心流程 Android.bp Blueprint 解析 ModuleFactory & 模块注册 PropertyStruct 属性绑定 build.go 生成规则 说明: 整个过程在$OUT下 生成build.ninja 最终由Ninja执行。 Ninja 执行

4.3 Android.bp语法基础

Android.bp使用类似JSON的语法,但更简洁(无引号、无逗号强制)。每个文件可以包含多个模块定义。基本结构:

// 模块类型 + 括号内的属性表
cc_library {
    name: "libfoo",
    srcs: ["src/foo.cpp", "src/bar.cpp"],
    cflags: ["-Wall", "-O2"],
    shared_libs: ["libutils"],
    export_include_dirs: ["include"],
    stl: "libc++_static",
}

关键语法元素:

元素说明示例
模块类型顶层标识,如cc_library、android_appcc_binary { ... }
属性名小写蛇形,全部可选/必选取决于模块类型name, srcs, cflags
列表用方括号,元素为字符串["a.cpp", "b.cpp"]
布尔true / falsestatic_executable: true
字符串双引号"liblog"
注释// 单行 或 /* ... */// 这是我的模块
名称唯一性:所有模块的name必须在全局唯一(包含跨分区)。若重复会导致构建错误。

4.4 常见模块类型与属性

模块类型用途核心属性示例
cc_libraryC/C++静态或共享库srcs, shared_libs, static_libs, export_include_dirs
cc_binary可执行文件srcs, shared_libs, static_libs, linkerflags
android_appAPK模块srcs, platform_apis, certificate, dex_preopt
java_libraryJava库(jar)srcs, static_libs, sdk_version
genrule自定义生成规则tool_files, cmd, out

4.5 build.go核心概念

build.go是Soong中最关键的扩展点,每个模块类型都有一个对应的Go文件实现Module接口:

type Module interface {
    Name() string
    // 返回该模块的无依赖规则
    GenerateBuildActions(ctx ModuleContext)
    // 其他接口方法
}

核心概念包括:

  • ModuleFactory:注册模块类型的工厂函数,例如cc.RegisterLibraryModule(…) 。
  • PropertyStruct:通过注解// property:"name"将bp属性映射到Go结构体字段,支持默认值和校验。
  • ModuleContext:提供了访问所有已注册模块、处理依赖、输出构建规则等API。
  • GenerateBuildActions:每个模块在此方法内通过ctx.Build(pctx, &builder)生成Ninja构建边。
调试技巧:查看Soong生成的Ninja文件有助于理解构建逻辑,位于$OUT/soong/build.ninja。可以使用ninja -f $OUT/soong/build.ninja -t targets all列出所有目标。

4.6 常见错误与解决方案

错误现象原因解决方法
“module name 'xxx' is not unique”两个Android.bp中定义了相同name重命名模块或通过//build/soong定位重复定义
“unknown module type 'cc_library2'”写错模块类型检查拼写,常见类型列表见4.4节
“srcs: unexpected value 'string'”srcs写成了字符串而非列表改为 ["file.cpp"]
依赖循环:circular dependency模块A依赖B,B又依赖A解耦依赖,或使用defaults模块简化
注意:不要在Android.bp中使用include或ifeq等Make风格宏。Soong不支持条件编译(除了android_available等少数内置属性)。需要条件编译请使用enabled属性或arch变量。

4.7 性能优化建议

  • 合理拆分模块:避免单个cc_library包含过多文件,尽量按功能拆分成多个小库,利用Ninja并行构建能力。
  • 使用shared_libs而非static_libs(当需要多个模块共享代码时),减少编译膨胀。
  • 指定arch变体:如arch: { arm: { cflags: ["-mthumb"] } },避免非目标架构的无效编译。
  • 使用defaults模块:将公共属性(如编译选项、依赖)集中定义,减少文件变动触发的重建范围。
  • 避免全局依赖:只声明实际使用的共享库,不要滥用whole_static_libs。

4.8 动手实践:编写首个Android.bp

  1. 在AOSP源码任意目录(如vendor/yourcompany/hello/Android.bp)创建文件。
  2. 编写一个简单可执行模块:
    cc_binary {
        name: "hello_world",
        srcs: ["main.cpp"],
        shared_libs: ["libcutils"],
    }
  3. 创建main.cpp:
    #include <cutils/log.h>
    int main() { ALOGD("Hello Soong"); return 0; }
  4. 在源码根目录执行make hello_world。
  5. 产物位于$OUT/system/bin/hello_world,可通过adb push到设备验证。
验证命令:
adb push $OUT/system/bin/hello_world /data/local/tmp/
adb shell chmod +x /data/local/tmp/hello_world
adb shell /data/local/tmp/hello_world

4.9 本章小结

Soong构建系统的核心是Android.bp→Blueprint→build.go→Ninja的四层转换。掌握bp语法(模块类型、属性表、列表与字符串表示)、理解build.go中Module接口和PropertyStruct机制,是编写和调试AOSP模块的基础。下一层级我们将深入模块依赖解析和编译资源管理。