29、构建系统迁移:从Android.mk迁移到Android.bp、迁移工具、迁移注意事项
嗯,说到构建系统迁移,这其实是我在AOSP开发中踩坑最多的一块。记得我刚接触Android构建时,满屏都是Android.mk,后来Google推Android.bp,我心里还嘀咕:这不是折腾人吗?但真正用上之后,我不得不承认——真香。
说白了,Android.mk是Makefile的变种,灵活但啰嗦。Android.bp是Blueprint的声明式配置,简洁且解析快。迁移的核心目标,就是把原来用Make语法写的模块定义,转成JSON风格的Blueprint格式。
29.1 为什么必须迁移?
Google在Android 7.0引入了Soong构建系统,从Android 10开始,新模块强制使用Android.bp。我参与过一个项目,因为遗留的Android.mk太多,导致增量编译慢得像蜗牛。迁移后,编译时间直接砍了30%。
你想想看,Android.mk每次解析都要启动Make进程,而Android.bp由Soong直接解析,速度快得多。而且Android.bp支持自动依赖分析,你少写很多LOCAL_STATIC_LIBRARIES之类的声明。
29.2 迁移工具:androidmk
Google官方提供了迁移工具——androidmk。它能把Android.mk转换成Android.bp。我习惯这么用:
# 单个文件转换
androidmk Android.mk > Android.bp
# 批量转换(我写了个小脚本)
for mk in $(find . -name "Android.mk"); do
bp_dir=$(dirname $mk)
androidmk $mk > $bp_dir/Android.bp
echo "Converted: $mk"
done
但注意,androidmk不是万能的。它只能处理80%的常见场景。剩下的20%,你得手动调整。
29.3 核心语法对照
我整理了一份对照表,你迁移时直接对着看就行:
| Android.mk 写法 | Android.bp 写法 | 说明 |
|---|---|---|
LOCAL_MODULE := mylib |
name: "mylib" |
模块名定义 |
LOCAL_SRC_FILES := a.cpp b.cpp |
srcs: ["a.cpp", "b.cpp"] |
源文件列表 |
LOCAL_STATIC_LIBRARIES := libfoo |
static_libs: ["libfoo"] |
静态库依赖 |
LOCAL_SHARED_LIBRARIES := libbar |
shared_libs: ["libbar"] |
动态库依赖 |
include $(BUILD_SHARED_LIBRARY) |
cc_library_shared { ... } |
模块类型声明 |
LOCAL_CFLAGS += -DFOO |
cflags: ["-DFOO"] |
编译标志 |
LOCAL_MODULE_TAGS := optional |
默认就是optional,可不写 | 模块标签 |
29.4 迁移注意事项(避坑指南)
我总结了5条核心注意事项,每一条都是真金白银换来的经验:
- 条件判断要拆模块
Android.bp不支持ifeq、ifneq这类条件语句。如果你原来的mk里有:
你得拆成两个模块,或者用ifeq ($(TARGET_ARCH), arm) LOCAL_CFLAGS += -DARM_SPECIFIC endifarch: { arm: { cflags: ["-DARM_SPECIFIC"] } }。 - include路径要显式声明
在mk里,你经常用LOCAL_C_INCLUDES := $(TOP)/external/libfoo/include。在bp里,建议用export_include_dirs或者直接写绝对路径。我遇到过因为路径解析不一致导致的编译失败。 - 模块依赖要完整
bp的依赖分析比mk严格。如果你漏写了某个shared_libs,链接阶段会报undefined reference。我的习惯是:先跑一遍m -j32,看报错再补依赖。 - 预编译模块要小心
LOCAL_PREBUILT_MODULE在bp里对应prebuilt_etc或prebuilt_usr_share等。注意src路径要写对,我见过有人把src: "libfoo.so"写成了src: "libfoo",结果找不到文件。 - 混合使用要谨慎
同一个目录下,不能同时存在Android.mk和Android.bp。Soong会优先读取bp。如果你还在用mk,要么全转,要么把mk改名备份。
29.5 迁移流程建议
我个人建议按这个步骤来:
- 先跑androidmk生成bp文件——拿到一个基础版本
- 手动修正条件判断——把ifeq等逻辑拆掉
- 编译验证——
mm或mma编译当前模块 - 修复依赖和路径——根据编译错误逐条修
- 清理旧mk文件——确认bp编译通过后,删除或备份mk
// Converted from Android.mk on 2024-01-01。这样以后排查问题,能快速知道来源。
29.6 知识体系图
下面这张图,帮你理清迁移的核心逻辑:
29.7 一个完整的迁移示例
给你看个实际例子。这是我从一个项目里摘出来的:
原来的 Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := my_native_lib
LOCAL_SRC_FILES := \
src/core.cpp \
src/utils.cpp \
src/network.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_STATIC_LIBRARIES := libprotobuf
LOCAL_SHARED_LIBRARIES := liblog libcutils
ifeq ($(TARGET_BUILD_VARIANT),user)
LOCAL_CFLAGS += -DRELEASE_BUILD
endif
include $(BUILD_SHARED_LIBRARY)
转换后的 Android.bp:
cc_library_shared {
name: "my_native_lib",
srcs: [
"src/core.cpp",
"src/utils.cpp",
"src/network.cpp",
],
export_include_dirs: ["include"],
static_libs: ["libprotobuf"],
shared_libs: [
"liblog",
"libcutils",
],
target: {
user: {
cflags: ["-DRELEASE_BUILD"],
},
},
}
看到没?原来的ifeq条件判断,在bp里用target: { user: { ... } }替代了。这就是迁移的核心技巧——把条件逻辑转换成bp支持的属性。
嗯,迁移这件事,说白了就是一次性的投入。虽然刚开始有点麻烦,但迁移完之后,你会发现构建速度快了,代码也更清晰了。如果你在迁移中遇到什么奇怪的问题,欢迎来交流。
公众号:蓝海资料掘金营,微信deep3321