告别Android.mk,手把手教你用Android.bp和Soong搞定模块化编译(附完整语法指南)
告别Android.mk手把手教你用Android.bp和Soong搞定模块化编译附完整语法指南在Android系统开发的演进历程中构建系统的革新始终是提升开发效率的关键。曾几何时Android.mk文件作为Makefile的变体承载了无数开发者的编译记忆。但随着项目规模膨胀和模块复杂度提升这种基于规则的构建方式逐渐暴露出维护成本高、解析效率低等问题。2016年Google正式引入Soong构建系统配合Blueprint语法Android.bp和Ninja构建工具形成了新一代声明式编译框架。本文将带您深入这套现代工具链从语法对比、迁移策略到实战技巧全面掌握模块化编译的进阶之道。1. 为何需要从Makefile迁移到Blueprint传统Android.mk文件采用Makefile语法通过显式规则定义构建过程。这种命令式编程虽然灵活但在处理大型项目时存在明显短板解析性能瓶颈GNU Make需要递归解析整个Makefile树AOSP全量编译时可能加载超过3万行脚本隐式依赖问题变量作用域难以追踪LOCAL_前缀的变量可能被意外修改条件编译臃肿大量ifeq/ifneq嵌套导致可读性急剧下降跨模块协作困难静态库、共享库的传递性依赖需要手动维护相比之下Soong/Blueprint方案具有三大核心优势声明式语法只需定义要什么无需编写怎么做并行解析Go语言实现的Soong可并发处理模块定义精确依赖自动构建模块间依赖图避免冗余编译典型场景对比# Android.mk示例 LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : libdemo LOCAL_SRC_FILES : $(call all-c-files-under, src) LOCAL_SHARED_LIBRARIES : liblog libcutils include $(BUILD_SHARED_LIBRARY)对应Blueprint语法// Android.bp示例 cc_library_shared { name: libdemo, srcs: [src/**/*.c], shared_libs: [liblog, libcutils], }代码量减少40%的同时后者还自动获得以下能力递归查找src目录下所有C文件精确的依赖版本管理模块可见性控制2. Android.bp语法精要Blueprint语法采用类似JSON的键值对结构但增加了类型系统和模块化支持。以下通过典型模式解析其核心要素2.1 基础模块定义所有构建目标都表现为模块常见模块类型包括模块类型描述关键属性cc_binaryC/C可执行文件srcs, static_libs, ldflagscc_libraryC/C静态/动态库export_include_dirsjava_libraryJava库不含资源installableandroid_app完整Android应用manifest, resource_dirsfilegroup文件集合不编译srcs, export_srcs示例定义一个带单元测试的库模块cc_library { name: libmath, srcs: [ vector.cpp, matrix.cpp, ], cflags: [-Wall, -O3], test_suites: [general-tests], header_libs: [libutils_headers], }2.2 条件编译实现Blueprint通过arch和target两个维度支持条件配置prebuilt_etc { name: default_network, src: config/network/default_$(TARGET_ARCH).conf, arch: { arm: { src: config/network/arm_default.conf }, x86: { src: config/network/x86_special.conf }, }, product_variables: { eng: { additional_conf: true }, }, }注意条件表达式不支持完整逻辑运算复杂场景应通过Go插件扩展2.3 高级特性应用变量与模板复用// 顶层变量定义 common_flags [-DLOG_LEVEL3] cc_binary { name: daemon, srcs: [main.cpp], cflags: common_flags [-DAUDIO_ENABLED], } // 模块继承 template(service_template) { cc_binary { name: module_name _service, relative_install_path: hw, init_rc: [module_name .rc], } } service_template { name: audio, }文件操作函数genrule { name: version_generator, tools: [build/scripts/gen_version.sh], cmd: $(location gen_version.sh) $(in) $(out), srcs: [VERSION], out: [version.h], }3. 迁移实战从.mk到.bp迁移过程需要系统化的策略以下是经过验证的五步法3.1 准备工作安装转换工具# 在AOSP根目录执行 m blueprint_tools export PATH$PATH:out/soong/host/linux-x86/bin生成初始转换androidmk Android.mk Android.bp提示自动转换工具仅能处理60%左右的基础语法需要人工校验3.2 典型模式转换对照表Android.mk模式Android.bp等效实现注意事项$(call all-subdir-java-files)java_library { srcs: [**/*.java] }需设置recursive_devices: trueLOCAL_JNI_SHARED_LIBRARIESjni_libs: [libname]需确保依赖库已导出LOCAL_MODULE_TAGS : optionalrequired: false默认即为optionalinclude $(BUILD_PACKAGE)android_app {}需显式指定资源目录3.3 复杂场景处理多架构差异化编译cc_library { name: libcodec, srcs: [common.cpp], target: { android: { srcs: [android_specific.cpp], cflags: [-DPLATFORM_ANDROID], }, linux_glibc: { srcs: [linux_specific.cpp], }, }, arch: { arm: { srcs: [arm/neon.cpp], cflags: [-mfpuneon], }, x86: { srcs: [x86/sse2.cpp], }, }, }动态源码生成// 先定义生成规则 genrule { name: proto_srcs, tools: [protoc], cmd: mkdir -p $(genDir) $(location protoc) --cpp_out$(genDir) $(in), srcs: [message.proto], out: [message.pb.cc, message.pb.h], } // 再引用生成文件 cc_library { name: libprotobuf, generated_sources: [proto_srcs], generated_headers: [proto_srcs], }4. 调试与优化技巧4.1 构建过程可视化使用Soong UI工具分析依赖关系# 生成模块依赖图 m nothing out/soong/soong_ui --graphmodule.dot dot -Tpng module.dot -o module.png常见问题诊断命令命令用途输出示例m dumpvars-查看模块最终变量值PLATFORM_SDK_VERSION34ninja -t deps out/.../module显示模块所有依赖input: system/core/libcutilsm analyze --module构建耗时分析compile: 12.3s (34.5%)4.2 性能调优参数在build/soong/soong.config中添加# 并行编译控制 NumJobs 24 # 通常设为CPU核心数的1.5倍 # 内存限制单位MB PoolHighMem 4096 # 缓存配置 UseGoma true GomaDir /var/goma4.3 常见陷阱解决方案问题1ERROR: Module libfoo missing dependencies: [libbar]解决方案// 在被依赖模块添加可见性声明 cc_library { name: libbar, visibility: [//path/to/dependent/module], }问题2头文件修改后未触发重新编译正确做法cc_library { name: libcorrect, export_include_dirs: [include], local_include_dirs: [private_include], }问题3生成的Ninja文件过大优化方案# 在envsetup.sh中添加 export NINJA_ARGS-t compact5. 进阶模块化实践5.1 组件化构建配置典型的多模块项目结构project/ ├── Android.bp # 根配置 ├── core/ # 核心组件 │ ├── Android.bp │ └── src/ ├── feature/ # 功能模块 │ ├── feature1/ │ │ ├── Android.bp │ │ └── ... │ └── feature2/ │ └── ... └── product/ # 产品定制 └── variant1/ └── Android.bp根目录配置示例// 声明子目录模块 subdirs [core, feature/*, product/variant1] // 产品差异化配置 product_variables { target_arch: { arm64: { subdirs [feature/neon] }, }, debuggable: { true: { cflags: [-DDEBUG] }, }, }5.2 插件化扩展通过Go编写Soong插件实现自定义规则创建build/soong/plugins/demo.gopackage demo import ( android/soong/android android/soong/cc ) func init() { android.RegisterModuleType(custom_library, customLibraryFactory) } func customLibraryFactory() android.Module { module : cc.NewLibrary(android.HostAndDeviceSupported) android.AddLoadHook(module, customProps) return module } func customProps(ctx android.LoadHookContext) { type props struct { ExtraFlags []string } p : props{} ctx.CustomProps(p) if len(p.ExtraFlags) 0 { ctx.Module().(*cc.Library).Flags.Local.CFlags append( ctx.Module().(*cc.Library).Flags.Local.CFlags, p.ExtraFlags...) } }在Android.bp中使用custom_library { name: libdemo, srcs: [special.cpp], extra_flags: [-DSPECIAL_FEATURE1], }5.3 与Bazel的协作模式在混合构建环境中可通过以下方式桥接// 声明Bazel构建目标 bazel_module { name: tensorflow_lite, label: //tensorflow/lite:framework, output: libtensorflowlite.so, } // 本地模块依赖外部构建 cc_binary { name: ml_app, srcs: [main.cpp], shared_libs: [tensorflow_lite], }在AOSP的演进路线中构建系统始终是提升开发效率的核心环节。从个人经验来看成功迁移到Soong的关键在于转变思维模式——从如何构建到声明构建目标。实践中最大的挑战往往不是语法转换而是重构模块边界和依赖关系。建议在复杂项目中采用渐进式迁移先转换叶子模块再处理层级依赖同时充分利用bp2build工具进行验证。