从Android.mk到Android.bp:Google为什么这么做?
一、主要差异
Android 编译系统从 Makefile (.mk) 转向 Blueprint (.bp) 是一个重要的变革,旨在提高构建系统的性能、可维护性和扩展性。
1. 语法与结构
| 特性 | Android.mk |
Android.bp |
|---|---|---|
| 配置语言 | 基于 GNU Make | 基于 JSON 样式的声明式语法 |
| 可读性 | 命令式,依赖 Makefile 的规则,逻辑复杂 | 声明式,语法简单,容易理解 |
| 灵活性 | 高度灵活,可写复杂逻辑、脚本 | 主要依赖于 Blueprint 模块,逻辑受限制 |
| 可维护性 | 大型工程容易出现代码复杂、维护成本高 | 结构清晰,模块化设计,适合大规模项目 |
2. 工具支持
| 特性 | Android.mk |
Android.bp |
|---|---|---|
| 构建工具 | 基于 GNU Make | 基于 Soong 构建系统 |
| 依赖解析 | 手动处理依赖 | 自动解析依赖关系 |
| 异常提示 | 异常诊断较少,调试困难 | 异常信息详细,调试较为友好 |
| 构建性能 | 串行化构建速度慢,支持并行有限 | 高效并行构建,性能大幅提升 |
3. 功能支持
| 特性 | Android.mk |
Android.bp |
|---|---|---|
| 动态生成 | 支持复杂的动态构建逻辑 | 动态生成能力受限,但支持全局模块化定义 |
| 模块依赖管理 | 需要手动管理依赖 | 声明式自动处理依赖,支持跨模块构建优化 |
| 跨平台支持 | GNU Make 对其他平台兼容性较强 | 专为 Android 构建优化,跨平台能力稍弱 |
二、Google 为什么更换为Android.bp
Google 从 Android.mk 转向 Android.bp 的主要原因是为了克服 Android.mk 的技术限制,提高整个构建系统的效率和可维护性。以下是详细的原因:
1. 提升构建性能
Android.mk
构建系统基于 GNU Make,其主要瓶颈在于:- 构建依赖解析效率低,尤其是在大型项目中。
- 并行构建支持有限,瓶颈显著。
Android.bp
基于 Soong 构建系统,利用了 Go 语言的高性能并行处理能力,能够极大地缩短构建时间。
2. 改进可维护性
Android.mk
文件逻辑复杂,灵活性高但容易造成滥用,导致代码难以维护。Android.bp
使用声明式语法,减少了复杂逻辑和语法错误的可能性,并通过模块化设计提升了代码的可读性和可维护性。
3. 增强可扩展性
Android.mk的灵活性使其难以适应复杂的现代构建需求,比如针对不同目标架构的优化。Android.bp通过模块化的构建描述方式,更容易实现功能扩展,如新增构建目标、定制化工具链支持等。
4. 支持大规模工程
Android 项目规模庞大,包含数百万行代码和上千个模块。传统的 Makefile 系统在管理这样复杂的依赖关系时显得吃力,而 Soong 系统设计上专为 Android 的复杂性量身定制,能更高效地管理模块间的依赖关系。
5. 一致性与规范化
Android.mk的灵活性导致不同团队的编写风格差异较大,缺乏统一标准。Android.bp强制采用声明式语法,降低了风格分歧,提高了代码一致性和项目的规范化水平。
6. 拥抱现代化技术
- GNU Make 是一个相对老旧的技术,存在一些设计上的历史包袱,难以满足现代构建系统的需求。
- Soong 构建系统基于 Go 语言开发,更符合现代软件工程对性能和灵活性的要求,同时可以利用 Go 的优势开发更多功能。
下面我们来看下bp的一些基础语法:Android.bp 文件以模块为单位组织代码,每个模块描述一种构建规则。模块的结构如下:
模块类型 {
属性1: "值",
属性2: ["值1", "值2"],
属性3: {
子属性: "值"
},
}
- 模块类型:定义模块的构建类型,如 cc_library, cc_binary, java_library 等。
- 属性:描述模块的配置细节,如源码路径、依赖关系等。
- 数据类型:
- 字符串:"值"
- 字符串数组:["值1", "值2"]
- 嵌套结构:{} 表示子属性或对象。
三、常用模块类型
1. C/C++ 模块
| 模块类型 | 描述 |
|---|---|
cc_library |
定义一个 C/C++ 静态库或共享库。 |
cc_binary |
定义一个 C/C++ 可执行文件。 |
cc_library_shared |
定义一个共享库(.so 文件)。 |
cc_library_static |
定义一个静态库(.a 文件)。 |
示例:定义一个共享库
cc_library_shared {
name: "example_shared_lib",
srcs: ["example.cpp"],
cflags: ["-Wall", "-O2"],
shared_libs: ["libc++"],
static_libs: ["libutils"],
}
2. Java 模块
| 模块类型 | 描述 |
|---|---|
java_library |
定义一个 Java 库模块。 |
android_app |
定义一个 Android 应用(生成 APK)。 |
java_binary |
定义一个 Java 可执行文件。 |
示例:定义一个 Java 库
java_library {
name: "example_java_lib",
srcs: ["Example.java"],
sdk_version: "current",
static_libs: ["android-support-v4"],
}
3. Prebuilt 模块
| 模块类型 | 描述 |
|---|---|
prebuilt_etc |
定义预构建的配置文件或资源文件。 |
prebuilt_library_shared |
引入预构建的共享库(.so 文件)。 |
prebuilt_binary |
引入预构建的可执行文件。 |
示例:引入一个预构建共享库
prebuilt_library_shared {
name: "example_prebuilt_lib",
srcs: ["prebuilt/example.so"],
shared_libs: ["libc++"],
}
4. 其他模块类型
| 模块类型 | 描述 |
|---|---|
filegroup |
定义一组文件,便于其他模块引用。 |
genrule |
自定义构建规则,运行命令生成输出。 |
android_library |
定义 Android 专用的 Java 库。 |
示例:定义一个文件组
filegroup {
name: "example_files",
srcs: ["src/file1.txt", "src/file2.txt"],
}
四、常用属性详解
以下是 Android.bp 中常用属性及其用途:
1. 基础属性
| 属性名 | 数据类型 | 描述 |
|---|---|---|
name |
字符串 | 模块的名称,必须唯一。 |
srcs |
字符串数组 | 指定源文件路径,可以使用通配符。 |
cflags |
字符串数组 | C/C++ 编译选项,例如警告或优化参数。 |
sdk_version |
字符串 | 指定编译时使用的 Android SDK 版本。 |
shared_libs |
字符串数组 | 依赖的共享库(动态链接)。 |
static_libs |
字符串数组 | 依赖的静态库(静态链接)。 |
2. 依赖管理
| 属性名 | 数据类型 | 描述 |
|---|---|---|
deps |
字符串数组 | 指定模块的依赖关系。 |
export_include_dirs |
字符串数组 | 将指定目录的头文件暴露给依赖该模块的其他模块。 |
generated_sources |
字符串数组 | 指定由其他模块生成的源文件。 |
3. 平台相关属性
| 属性名 | 数据类型 | 描述 |
|---|---|---|
arch |
嵌套结构 | 根据目标架构设置特定的属性。 |
target |
嵌套结构 | 根据目标平台(host/device)设置特定的属性。 |
示例:根据架构设置源文件
cc_library {
name: "example_arch_specific",
arch: {
arm: {
srcs: ["src/arm_specific.cpp"],
},
x86: {
srcs: ["src/x86_specific.cpp"],
},
},
}
4. 自定义构建规则
| 属性名 | 数据类型 | 描述 |
|---|---|---|
cmd |
字符串 | 指定构建命令,仅适用于 genrule 模块。 |
out |
字符串数组 | 指定命令的输出文件。 |
示例:使用 genrule 自定义规则
genrule {
name: "example_genrule",
srcs: ["input.txt"],
cmd: "cat $(in) > $(out)",
out: ["output.txt"],
}
五、高级用法
1. 模块继承与复用
可以使用 defaults 模块来定义一组通用的默认属性,供其他模块继承。
示例:使用 defaults
cc_defaults {
name: "common_defaults",
cflags: ["-Wall", "-O2"],
shared_libs: ["libc++"],
}
cc_library {
name: "example_with_defaults",
srcs: ["example.cpp"],
defaults: ["common_defaults"],
}
2. 条件编译
使用 arch 或 target 属性实现不同平台的条件编译。
示例:针对不同目标平台编译
cc_binary {
name: "example_target_specific",
target: {
host: {
srcs: ["src/host.cpp"],
},
android: {
srcs: ["src/android.cpp"],
},
},
}