4. 编译系统入门

Android 的编译系统在 8.0 之后经历了一次重要演进:从纯 Make 体系逐步迁移到以 Soong(Android.bp)为主、Make(Android.mk)为辅的混合体系。在 Snapdragon BSP 工程中,高通平台既保留了部分 Android.mk 定义的模块,也大量使用 Android.bp 描述 HAL、库和可执行文件,因此理解两套系统的分工与协作,是后续修改编译配置、定位产物路径的基础。

Soong 与 Make 的分工

Soong 使用 Android.bp 文件,语法接近 JSON,声明式、无分支无循环,由 soong_ui 解析后生成 Ninja 构建文件,最终由 Ninja 执行编译。Make 使用 Android.mk,支持条件判断和函数,由 GNU Make 解析。两者并非互相替代,而是通过 build/make/core/ 中的桥接机制协同:Soong 会把 Android.bp 中定义的模块导出为 Make 可见的变量,Make 也可以调用 Soong 生成的模块。

一个典型的 Android.bp 模块定义如下,它声明了一个名为 libsnapdsp 的共享库,并依赖 liblog:

cc_library_shared {
    name: "libsnapdsp",
    srcs: ["src/snap_dsp.c"],
    shared_libs: ["liblog"],
    vendor: true,
}

其中 vendor: true 表示该模块编译到 vendor 分区,产物会落在 out/target/product/<device>/vendor/lib64/ 下。若写成 vendor_available: true,则同时提供 vendor 与 system 两个变体。理解这些属性,直接决定了模块最终被安装到哪个分区。

lunch 目标选择

编译前必须先选择目标,这一步由 lunch 完成。执行 source build/envsetup.sh 后,lunch 会列出所有可用的 product 与 build variant 组合。对于高通平台,常见目标形如 qcom_xxx-userdebug,其中 qcom_xxx 是产品名,userdebug 是编译变体。

编译变体有四种:user(发布版,无 root、无调试)、userdebug(可 root、可调试,最常用于开发)、eng(工程版,含额外调试工具)、tests(测试专用)。选择 userdebug 时,系统会开启 adb root 与 ro.debuggable=1,便于抓取日志和替换模块。

lunch 选择后,环境变量 TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_DEVICE 会被设置,后续编译命令依赖这些变量确定输出目录。例如选择 qcom_xxx-userdebug 后,产物根目录为 out/target/product/qcom_xxx/。

编译命令与产物路径

最常用的编译命令是 make 或 m(envsetup.sh 提供的封装)。在源码根目录执行:

source build/envsetup.sh
lunch qcom_xxx-userdebug
make -j8

其中 -j8 表示并行 8 个任务,通常设为 CPU 核心数的 1~2 倍。若只编译某个模块,可用 m libsnapdsp 或 mm(在模块目录下执行)。mmm 则用于编译指定目录下的所有模块。

编译产物按分区和类型分布,关键路径如下:

  • out/target/product/<device>/system/:system 分区镜像与文件
  • out/target/product/<device>/vendor/:vendor 分区镜像与文件
  • out/target/product/<device>/system.img、vendor.img:分区镜像
  • out/target/product/<device>/obj/:中间目标文件(.o、.so 等)
  • out/target/product/<device>/obj/KERNEL_OBJ/:内核编译中间产物
  • out/target/product/<device>/boot.img、dtbo.img:启动相关镜像

以 libsnapdsp 为例,编译后可在 out/target/product/qcom_xxx/vendor/lib64/libsnapdsp.so 找到产物,中间文件在 out/target/product/qcom_xxx/obj/SHARED_LIBRARIES/libsnapdsp_intermediates/ 下。若模块未出现在预期路径,通常是因为 vendor 属性未设置或模块未被产品继承。

在 Snapdragon BSP 中,高通平台常通过 device/qcom/<target>/ 下的 AndroidBoard.mk 和 BoardConfig.mk 控制分区与模块包含关系。修改 BoardConfig.mk 中的 BOARD_VENDOR_* 变量会影响 vendor 分区内容,需重新 lunch 后编译才生效。

编译流程与产物关系

下图展示了从 lunch 选择到最终镜像产出的关键路径,以及 Soong 与 Make 在其中的位置。图中左侧为输入配置,中间为解析与构建阶段,右侧为产物落盘位置,便于对照实际目录结构理解。

Android 编译流程与产物路径(Snapdragon BSP) 输入配置 Android.bp / Android.mk BoardConfig.mk device/qcom/<target>/ product 继承关系 lunch 目标选择 qcom_xxx-userdebug TARGET_PRODUCT TARGET_BUILD_VARIANT TARGET_DEVICE 解析与构建 soong_ui 解析 bp 生成 Ninja 文件 Make 桥接模块 Ninja 并行编译 产物路径:out/target/product/<device>/ system/ system.img app/ lib/ bin/ vendor/ vendor.img lib64/ bin/ etc/ obj/ SHARED_LIBRARIES/ EXECUTABLES/ 镜像文件 boot.img dtbo.img 示例:libsnapdsp 模块产物 最终产物:out/target/product/qcom_xxx/vendor/lib64/libsnapdsp.so 中间文件:out/target/product/qcom_xxx/obj/SHARED_LIBRARIES/libsnapdsp_intermediates/ 编译输出

从图中可以看出,lunch 选择决定了 TARGET_PRODUCT 与 TARGET_BUILD_VARIANT,进而决定输出根目录;Soong 与 Make 共同解析模块定义,最终由 Ninja 执行编译并落盘到对应分区目录。掌握这条链路后,当模块未出现在预期路径时,可以按“lunch 是否正确 → 模块属性是否匹配分区 → 产品是否继承该模块”的顺序排查。

修改 Android.bp 或 Android.mk 后,若只执行 m 未生效,可尝试 m installclean 清理安装产物后重编。修改 BoardConfig.mk 或产品继承关系后,必须重新执行 lunch,否则环境变量仍指向旧配置。

实际开发中,建议先用 m libsnapdsp 单独验证模块能否编译通过,再执行整包 make -j8。整包编译耗时较长,可借助 ccache 加速;高通平台通常已在 BoardConfig.mk 中开启 USE_CCACHE。编译完成后,通过 adb root 与 adb remount 可将新生成的 .so 推入设备验证,路径与产物路径一致,便于快速迭代。