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%,你得手动调整。

我曾经踩过的坑:直接用androidmk转换后,发现模块编译不过。原因是原mk里用了条件判断(ifeq),而bp不支持条件逻辑。这类情况必须手动拆分模块或使用build flag。

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条核心注意事项,每一条都是真金白银换来的经验:

  1. 条件判断要拆模块
    Android.bp不支持ifeqifneq这类条件语句。如果你原来的mk里有:
    ifeq ($(TARGET_ARCH), arm)
    LOCAL_CFLAGS += -DARM_SPECIFIC
    endif
    你得拆成两个模块,或者用arch: { arm: { cflags: ["-DARM_SPECIFIC"] } }
  2. include路径要显式声明
    在mk里,你经常用LOCAL_C_INCLUDES := $(TOP)/external/libfoo/include。在bp里,建议用export_include_dirs或者直接写绝对路径。我遇到过因为路径解析不一致导致的编译失败。
  3. 模块依赖要完整
    bp的依赖分析比mk严格。如果你漏写了某个shared_libs,链接阶段会报undefined reference。我的习惯是:先跑一遍m -j32,看报错再补依赖。
  4. 预编译模块要小心
    LOCAL_PREBUILT_MODULE在bp里对应prebuilt_etcprebuilt_usr_share等。注意src路径要写对,我见过有人把src: "libfoo.so"写成了src: "libfoo",结果找不到文件。
  5. 混合使用要谨慎
    同一个目录下,不能同时存在Android.mkAndroid.bp。Soong会优先读取bp。如果你还在用mk,要么全转,要么把mk改名备份。

29.5 迁移流程建议

我个人建议按这个步骤来:

  1. 先跑androidmk生成bp文件——拿到一个基础版本
  2. 手动修正条件判断——把ifeq等逻辑拆掉
  3. 编译验证——mmmma编译当前模块
  4. 修复依赖和路径——根据编译错误逐条修
  5. 清理旧mk文件——确认bp编译通过后,删除或备份mk
小技巧:迁移时,我习惯在bp文件头部加一行注释:// Converted from Android.mk on 2024-01-01。这样以后排查问题,能快速知道来源。

29.6 知识体系图

下面这张图,帮你理清迁移的核心逻辑:

Android.mk → Android.bp 迁移核心逻辑 Android.mk • Makefile语法 • 支持条件判断 • 解析慢 • 依赖手动声明 • 灵活但啰嗦 迁移工具 • androidmk 自动转换 • 覆盖80%场景 • 需手动修正20% • 编译验证 Android.bp • Blueprint语法 • 声明式配置 • 解析快 • 自动依赖分析 • 简洁易维护 转换 手动修正 ⚠ 注意:条件判断需拆模块、依赖要完整、预编译路径要写对

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支持的属性。

我的经验:迁移完成后,一定要做一次全量编译。因为bp的依赖解析和mk不同,有时候你以为没问题,实际链接阶段会报错。我吃过这个亏,后来养成了“迁移完先全编”的习惯。

嗯,迁移这件事,说白了就是一次性的投入。虽然刚开始有点麻烦,但迁移完之后,你会发现构建速度快了,代码也更清晰了。如果你在迁移中遇到什么奇怪的问题,欢迎来交流。


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