编译脚本定制:自定义编译命令、扩展编译系统、添加新功能
Android 构建系统不是死的。说白了,它是一套可以让你随意折腾的框架。我见过很多团队,拿到 AOSP 源码后第一件事就是改构建脚本——加个自定义命令、集成内部工具、或者干脆重写部分逻辑。今天我们就聊聊怎么在 Android 构建系统里「加料」。
为什么需要定制编译脚本?
你可能觉得 Google 给的编译命令够用了。嗯,大部分场景确实够。但实际项目中,总有些特殊需求:
- 编译完成后自动把镜像推到测试机
- 集成第三方静态检查工具
- 生成自定义的版本号或签名逻辑
- 并行编译多个产品变体
我在做车载系统时,就遇到过需要同时编译三个不同屏幕尺寸的镜像。默认的 make 命令一次只能搞一个,手动跑三遍太傻了。于是我们加了个自定义命令,一行搞定。
理解构建系统的扩展点
Android 构建系统基于 Make 和 Soong(Blueprint)。扩展它,你得知道往哪插。核心扩展点有三个:
| 扩展点 | 位置 | 作用 |
|---|---|---|
| BoardConfig.mk | device/<vendor>/<product>/ | 定义板级变量、编译选项 |
| Android.mk / Android.bp | 各模块目录 | 添加模块、自定义编译规则 |
| vendorsetup.sh | device/<vendor>/<product>/ | 添加 lunch 选项、环境变量 |
我个人习惯把自定义命令放在 device/<vendor>/common/ 下,这样所有产品都能复用。
实战:添加一个自定义编译命令
假设我们要加一个 mymake 命令,编译完成后自动把镜像拷贝到指定目录。怎么做?
第一步,在 device/<vendor>/common/ 下创建 custom_commands.mk:
# 自定义编译命令
.PHONY: mymake
mymake: droid
@echo "编译完成,开始拷贝镜像..."
cp $(OUT)/target/product/$(TARGET_DEVICE)/*.img /mnt/shared/images/
@echo "镜像已拷贝到 /mnt/shared/images/"
第二步,在产品的 BoardConfig.mk 末尾引入:
include device/<vendor>/common/custom_commands.mk
然后你就可以在源码根目录下执行 source build/envsetup.sh && lunch 后,直接敲 mymake 了。
$(filter) 或 $(if) 函数。我曾经写过一个带版本号参数的命令,方便 CI 系统调用。
扩展 Soong 构建系统
Soong 是 Android 7.0 之后引入的构建系统,用 Go 语言写的。扩展它需要写 Go 代码,但别怕,没那么复杂。
举个例子,我想在编译某个模块时,自动生成一个头文件。在 Android.bp 里可以这样:
cc_binary {
name: "my_daemon",
srcs: ["main.cpp"],
generated_headers: ["my_header_gen"],
}
genrule {
name: "my_header_gen",
cmd: "python3 $(location gen_header.py) $(out)",
out: ["my_header.h"],
tools: ["gen_header.py"],
}
这里 genrule 就是 Soong 提供的扩展点。你可以用它执行任意脚本,生成文件、检查代码、甚至发通知。
genrule 里 cmd 不能写太复杂的逻辑。我踩过坑——写了个 200 行的 shell 命令,结果调试到崩溃。建议把复杂逻辑写成独立脚本,用 tools 引用。
添加新功能:集成代码检查工具
很多团队希望在编译时自动跑静态检查。比如用 cppcheck 检查 C++ 代码。我们可以通过 Android.mk 的 LOCAL_POST_LINK_CMD 来实现:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := my_service
LOCAL_SRC_FILES := my_service.cpp
# 编译后自动运行 cppcheck
LOCAL_POST_LINK_CMD := cppcheck --enable=all $(LOCAL_SRC_FILES)
include $(BUILD_EXECUTABLE)
这样每次编译 my_service 时,都会自动跑一遍 cppcheck。如果检查出问题,编译会失败。
但说实话,我不建议在每次编译时都跑全量检查。太慢了。更好的做法是加一个独立的目标:
.PHONY: check_all
check_all:
find $(TOP)/system/ -name "*.cpp" -exec cppcheck {} \;
然后在 CI 里单独调用 make check_all。这样开发时编译快,提交前再跑完整检查。
知识体系总览
下面这张图帮你理清整个定制流程:
避坑指南
定制编译脚本看着简单,但坑不少。我总结几个常见的:
- 不要修改 build/core/ 下的文件——那是构建系统的核心,升级 AOSP 时会被覆盖。我曾经手贱改过,结果升级时一堆冲突,后悔死了。
- 变量名别冲突——Android 构建系统用了大量全局变量。你自定义的变量最好加个前缀,比如
MY_或CUSTOM_。 - 注意并行编译——Make 的
-j参数会让多个目标并行执行。如果你的自定义命令依赖某个模块,记得加.NOTPARALLEL或显式声明依赖。 - 调试时用
$(info)——在 .mk 文件里加$(info 变量名=$(变量名)),编译时就能看到变量值。比猜快多了。
核心原则:定制编译脚本时,始终记住「不改核心,只做扩展」。这样升级 AOSP 时,你的定制代码还能继续用。
好了,关于编译脚本定制就聊这么多。你可以在自己的项目里试试,加个自定义命令,或者集成个检查工具。动手做一遍,比看十遍文章都管用。