从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"],
},
},
}