20、构建系统:Android.bp 与 Soong,产品配置(Product Config),车载专属模块编译

各位好,我是你们的老朋友。今天我们来聊聊 Android Automotive OS 的构建系统。说实话,我刚接触 Android 构建时,也被 Makefile 和 Android.mk 搞得晕头转向。后来 Google 推出了 Soong 和 Android.bp,我才觉得这事儿终于靠谱了。

这一章,我会带你搞懂三件事:Android.bp 怎么写产品配置怎么玩、以及车载模块怎么编译。嗯,都是实战中天天要用的东西。

20.1 从 Android.mk 到 Android.bp:为什么变?

老项目里,你看到的都是 Android.mk 文件。它基于 GNU Make,语法灵活但慢得要命。我印象特别深,有一次编译整个系统,光解析 Makefile 就花了快十分钟。

Soong 就是来救场的。它用 Ninja 做底层构建工具,解析速度提升了一个数量级。而 Android.bp 是 Soong 的配置文件,语法类似 JSON,简洁明了。

核心区别一句话:Android.mk 是过程式(告诉系统怎么做),Android.bp 是声明式(告诉系统要什么)。

举个例子,你写一个原生库,Android.mk 要写一堆 LOCAL_ 变量:

# Android.mk 老写法
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libmyfeature
LOCAL_SRC_FILES := myfeature.cpp
LOCAL_SHARED_LIBRARIES := libbase liblog
include $(BUILD_SHARED_LIBRARY)

换成 Android.bp,清爽多了:

// Android.bp 新写法
cc_library_shared {
    name: "libmyfeature",
    srcs: ["myfeature.cpp"],
    shared_libs: ["libbase", "liblog"],
}

我个人习惯,新项目一律用 Android.bp。除非你要维护老代码,否则别碰 Android.mk。

20.2 Android.bp 核心语法速览

Android.bp 的模块类型很多,但车载开发常用的就那么几种。我帮你整理了一下:

模块类型 用途 常见场景
cc_library_shared 编译动态库 (.so) 车载 HAL 实现、服务库
cc_binary 编译可执行文件 车载守护进程、测试工具
java_library 编译 Java 库 (jar) 车载系统服务、API 接口
android_app 编译 APK 车载 Launcher、设置应用
aidl_interface 编译 AIDL 接口 车载 HIDL/AIDL 服务定义

写 Android.bp 时,有几个字段特别容易踩坑:

  • name:模块名,全局唯一。我建议用项目前缀,比如 automotive_,避免冲突。
  • srcs:源文件列表。支持通配符,但别滥用,否则增量编译会变慢。
  • shared_libs:运行时依赖的动态库。注意,这里只写库名,不用写 lib 前缀和 .so 后缀。
  • static_libs:编译时静态链接的库。车载模块里,我建议尽量用动态库,节省镜像空间。

小技巧:如果你不确定某个字段怎么用,直接去源码里搜 Android.bp 文件,看看 Google 官方模块怎么写。比如 packages/services/Car/ 目录下就有很多车载专属的 bp 文件。

20.3 产品配置:Product Config 到底配什么?

产品配置,说白了就是告诉构建系统:我这台车要装哪些模块?

Android 的产品配置放在 device/ 目录下,每个 OEM 有自己的文件夹。比如:

device/
  └── mycompany/
      └── mycar/
          ├── AndroidProducts.mk
          ├── device.mk
          ├── BoardConfig.mk
          └── product.mk

这里最关键的是 product.mk。它定义了产品要包含哪些模块。举个例子:

# product.mk
PRODUCT_PACKAGES += \
    CarLauncher \
    CarSettings \
    CarService \
    libcarframework \
    automotive_can_hal \
    automotive_cluster_service

你看,车载专属模块都在这里列出来。如果你要加一个新模块,比如车载语音助手,就在 PRODUCT_PACKAGES 里加上模块名。

我曾经犯过一个错:在 product.mk 里加了一个模块,但忘记在 Android.bp 里定义 name。结果编译报错说找不到模块。嗯,从那以后我每次加模块都会先确认 bp 文件有没有写对。

20.4 车载专属模块编译实战

车载系统里,有些模块是手机没有的。比如 CAN 总线 HAL、仪表盘服务、车辆属性服务。这些模块的编译,有一些特殊之处。

20.4.1 车载 HAL 模块

车载 HAL 通常用 HIDL 或 AIDL 定义接口。以 CAN HAL 为例:

// hardware/interfaces/automotive/can/1.0/Android.bp
hidl_interface {
    name: "android.hardware.automotive.can@1.0",
    srcs: ["types.hal", "ICanBus.hal", "ICanController.hal"],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
    gen_java_constants: true,
}

注意 gen_java: true,这个会生成 Java 接口,供上层服务调用。如果你只做 C++ 实现,可以关掉它,加快编译速度。

20.4.2 车载系统服务

车载系统服务通常用 Java 写,编译成 APK 或 jar 包。比如 CarService:

// packages/services/Car/Android.bp
java_library {
    name: "car-service",
    srcs: [
        "src/**/*.java",
    ],
    libs: [
        "car-framework",
        "android.car",
    ],
    static_libs: [
        "can-utils",
    ],
    installable: true,
}

这里 installable: true 表示这个库会安装到系统镜像里。如果你只是编译测试用,可以设成 false。

20.4.3 车载专属编译选项

车载模块有时需要特殊的编译标志。比如,有些芯片不支持浮点运算,你得用软浮点:

cc_library_shared {
    name: "libmycarfeature",
    srcs: ["mycarfeature.cpp"],
    cflags: [
        "-msoft-float",
        "-O2",
    ],
    target: {
        android_arm: {
            cflags: ["-march=armv7-a"],
        },
        android_arm64: {
            cflags: ["-march=armv8-a"],
        },
    },
}

你看,target 字段可以针对不同架构设置不同参数。这在车载多平台适配时特别有用。

注意:车载模块的编译选项,一定要和硬件团队确认。我曾经因为 GCC 优化等级设太高,导致 CAN 驱动在特定芯片上跑飞。后来改成 -O1 才稳定。

20.5 知识体系总览

为了让你更直观地理解,我画了一张图,把本章的核心逻辑串起来:

Android Automotive OS 构建系统核心逻辑 Android.bp 模块定义 声明式语法,模块类型 Product Config 产品配置 PRODUCT_PACKAGES 列表 Board Config 板级配置 架构、内核、分区 Soong 构建引擎 解析 .bp → 生成 Ninja 文件 → 执行编译 车载 HAL 模块 CAN、VHAL、Cluster 车载系统服务 CarService、CarLauncher 系统镜像 system.img 输入 → 构建引擎 → 输出,三者缺一不可

这张图展示了构建系统的完整流程。左边是输入(Android.bp、Product Config、Board Config),中间是 Soong 引擎,右边是输出(车载模块和系统镜像)。

20.6 避坑指南与个人经验

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 模块名冲突:Android 源码里模块名是全局唯一的。如果你不小心和系统模块重名,编译会报错。我习惯在车载模块名前加 automotive_ 前缀。
  • 依赖缺失:在 product.mk 里加了模块,但模块依赖的库没加。编译时不会报错,但运行时就会 crash。建议用 m -j32 全量编译一次,确保所有依赖都满足。
  • 缓存问题:修改 Android.bp 后,有时增量编译不生效。我一般先 rm -rf out/ 再重新编译,虽然慢点但保险。
  • 车载专属模块的权限:有些车载模块需要 system 权限,记得在 Android.bp 里加 privileged: true,否则安装时会报错。

我的建议:刚开始接触车载构建系统时,别急着写复杂的 bp 文件。先找一个现成的车载模块,比如 CarLauncher,看看它的 Android.bp 怎么写,然后照着改。这样上手最快。

好了,这一章的内容就到这里。构建系统是 Android 开发的基石,搞懂了它,你就能随心所欲地往系统里加车载功能了。

公众号:蓝海资料掘金营,微信deep3321