深入理解AOSP中的Android.bp语法
引言
Android Open Source Project(AOSP)是Android系统的基础,其构建系统是实现系统定制与编译的重要工具。在早期的AOSP版本中,Android使用的是基于GNU Make的构建系统,即Android.mk文件来定义模块及其依赖。然而,随着系统规模和复杂性的增长,Makefile的局限性逐渐显现,Google引入了Soong构建系统,并以Android.bp文件替代了传统的Android.mk文件。
Android.bp 是一种基于声明的构建描述文件,与传统的Makefile相比,它更具可读性和灵活性。本文将深入探讨Android.bp语法、结构、模块定义及其在AOSP中的实际应用。
Android.bp文件的基本结构
Android.bp文件的核心思想是以模块为单位进行配置。每个模块可以描述一个库、可执行文件或其他目标构建对象。每个模块定义包含多个属性,用于指定源文件、依赖项、编译选项等。
cc_library {
name: "libexample",
srcs: ["example.cpp"],
shared_libs: ["liblog"],
}
在上述例子中,cc_library
定义了一个C/C++库模块,name
指定模块名称,srcs
指定源文件,shared_libs
则描述依赖的共享库。Soong系统会根据这些描述生成构建流程,并最终将这些模块链接在一起。
变量与属性
变量的定义
与传统的Makefile不同,Android.bp文件是声明式的,因此没有直接的变量赋值功能。但Soong支持常量定义,常常用于减少重复配置。例如,我们可以定义一个常量来共享源文件:
common_sources = ["foo.cpp", "bar.cpp"]
cc_library {
name: "libfoo",
srcs: common_sources,
}
cc_library {
name: "libbar",
srcs: common_sources,
}
常见属性
每个模块类型都有其特定的属性集,但以下是常见的几个属性:
• name: 模块名称,构建系统通过该名称引用模块。
• srcs: 模块的源文件列表,可以包含C/C++源文件、Java源文件等。
• deps: 依赖模块列表,指定该模块在编译时需要链接的其他模块。
• shared_libs: 共享库依赖,指定该模块运行时需要的动态链接库。
模块类型及其应用
Android.bp中支持多种模块类型,以下是几种常见模块类型的介绍:
1. cc_library: 用于构建C/C++库,可以是静态库或动态库。
2. java_library: 用于构建Java库。
3. android_library: 专门用于Android开发的Java库。
4. cc_binary: 用于构建C/C++可执行文件。
5. java_binary: 用于构建Java可执行文件。
每种模块类型都有特定的属性,允许开发者自定义编译方式和依赖管理。
示例:cc_library模块
cc_library {
name: "libmylibrary",
srcs: ["mylibrary.cpp"],
shared_libs: ["liblog"],
}
示例:java_library模块
java_library {
name: "mylibrary",
srcs: ["src/com/example/MyLibrary.java"],
libs: ["core-libart"],
}
在这些例子中,cc_library
模块用于编译C++代码,而java_library
用于构建Java类库。这两者通过shared_libs
和libs
属性指定了各自依赖的库文件。
依赖管理
在大型项目中,模块间的依赖管理至关重要。Android.bp提供了多种方式来管理模块之间的依赖。
• static_libs: 用于指定静态链接库依赖。
• shared_libs: 用于指定动态链接库依赖。
• deps: 用于一般性依赖,适用于Java和其他模块类型。
举例来说,如果一个模块依赖其他多个库:
cc_library {
name: "libfoo",
srcs: ["foo.cpp"],
static_libs: ["libbar", "libbaz"],
}
在这个例子中,libfoo
会静态链接libbar
和libbaz
库。
条件编译
Android.bp文件允许根据不同的目标设备或架构进行条件编译。通过target
和arch
字段,可以为不同的设备架构或操作系统版本配置不同的编译选项。
示例:为不同架构设置编译选项
cc_library {
name: "libexample",
srcs: ["example.cpp"],
target: {
android: {
srcs: ["example_android.cpp"],
},
linux_glibc: {
srcs: ["example_linux.cpp"],
},
},
arch: {
arm64: {
srcs: ["example_arm64.cpp"],
},
},
}
在这个例子中,libexample
库根据不同的目标系统使用不同的源文件进行编译。对于Android系统,会额外包含example_android.cpp
文件,而对于64位ARM架构,还会添加example_arm64.cpp
文件。
自定义构建步骤
在一些特定的场景中,可能需要自定义构建步骤。这时可以使用genrule
模块,通过执行自定义脚本生成构建文件。例如,我们可以用genrule
生成一个头文件:
genrule {
name: "generate_header",
cmd: "python generate_header.py > $(out)",
out: ["generated_header.h"],
}
在这个例子中,genrule
会调用Python脚本生成一个头文件,并将其作为构建过程中的输入文件。
与Soong的集成
Soong构建系统是Android.bp文件的解析和执行引擎。Soong基于Go语言编写,它解析Android.bp文件并生成Ninja构建文件。每个Android.bp文件的模块定义会被Soong解析成内部的Go结构体,随后被转换为低层次的Ninja规则。
Soong的优势在于其高效的模块管理和依赖解析能力。与传统的Makefile相比,Soong更加关注模块之间的依赖关系,并通过静态分析来优化构建过程。
常见问题及调试技巧
在编写Android.bp文件时,开发者可能会遇到以下常见问题:
1. 未定义的模块: 当某个模块引用了不存在的依赖模块时,Soong会报错。解决方案是检查依赖关系并确保所有依赖模块都已正确定义。
2. 模块属性不匹配: 不同模块类型有各自特定的属性。如果在cc_library中使用了java_library的属性,会导致解析错误。开发者需要仔细检查每个模块类型的属性文档。
3. 调试构建过程: Soong支持通过
m
命令加上-n
参数打印构建过程中的详细信息,帮助开发者分析依赖关系和构建顺序。
Android.mk与Android.bp语法差异
Android.bp 是 Android 系统中的新编译配置文件,用于替代 Android.mk。自 Android 7.0 起,Google 引入了 Ninja 和 Kati 构建系统,以提升编译效率,尤其在模块众多的大型项目中,Ninja 并发处理能力强于 Make。Android.bp 文件通过 Soong 和 Blueprint 工具解析,生成 Ninja 文件,用于高效并行编译。与支持复杂控制逻辑的 Android.mk 不同,Android.bp 采用纯声明式配置,简洁且不包含条件语句或循环控制。虽然当前版本仍支持 Android.mk,但未来可能完全转向 Android.bp,淘汰 Kati 工具。因此,开发者应尽早学习并掌握 Android.bp 的语法和用法,以适应未来的开发需求。
Android.bp与Android.mk语法对应规则
我们可能已经习惯了Android.mk 中的语法,现在要变更为 Android.bp, 为了便于理解,可以找到源码,查看Android.mk 与 Android.bp 语法对应规则, 源码位置: build/soong/androidmk/androidmk/android.go
中, 这里我只粘贴一部分,完整代码请查看源文件。
var moduleTypes = map[string]string{
"BUILD_SHARED_LIBRARY": "cc_library_shared",
"BUILD_STATIC_LIBRARY": "cc_library_static",
"BUILD_HOST_SHARED_LIBRARY": "cc_library_host_shared",
"BUILD_HOST_STATIC_LIBRARY": "cc_library_host_static",
"BUILD_HEADER_LIBRARY": "cc_library_headers",
"BUILD_EXECUTABLE": "cc_binary",
"BUILD_HOST_EXECUTABLE": "cc_binary_host",
"BUILD_NATIVE_TEST": "cc_test",
"BUILD_HOST_NATIVE_TEST": "cc_test_host",
"BUILD_NATIVE_BENCHMARK": "cc_benchmark",
"BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host",
"BUILD_JAVA_LIBRARY": "java_library_installable", // will be rewritten to java_library by bpfix
"BUILD_STATIC_JAVA_LIBRARY": "java_library",
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
"BUILD_RRO_PACKAGE": "runtime_resource_overlay",
"BUILD_CTS_EXECUTABLE": "cc_binary", // will be further massaged by bpfix depending on the output path
"BUILD_CTS_SUPPORT_PACKAGE": "cts_support_package", // will be rewritten to android_test by bpfix
"BUILD_CTS_PACKAGE": "cts_package", // will be rewritten to android_test by bpfix
"BUILD_CTS_TARGET_JAVA_LIBRARY": "cts_target_java_library", // will be rewritten to java_library by bpfix
"BUILD_CTS_HOST_JAVA_LIBRARY": "cts_host_java_library", // will be rewritten to java_library_host by bpfix
}
var prebuiltTypes = map[string]string{
"SHARED_LIBRARIES": "cc_prebuilt_library_shared",
"STATIC_LIBRARIES": "cc_prebuilt_library_static",
"EXECUTABLES": "cc_prebuilt_binary",
"JAVA_LIBRARIES": "java_import",
"APPS": "android_app_import",
"ETC": "prebuilt_etc",
}
语法差异
Android.mk 与 Android.bp 在 Android 构建系统中有着相似的功能,但它们的语法和工作机制存在明显差异。以下是主要的语法对比与差异:
1. 模块编译类型
• Android.mk 使用
include
语句来指定模块类型,例如:•
include $(BUILD_JAVA_LIBRARY)
编译为 Java 库•
include $(BUILD_SHARED_LIBRARY)
编译为动态库• Android.bp 采用模块化的语法结构,每种模块类型对应一个块,例如:
•
java_library { ... }
用于 Java 库•
cc_library_shared { ... }
用于动态库
2. 文件路径
• Android.mk 中使用
LOCAL_C_INCLUDES
和LOCAL_EXPORT_C_INCLUDE_DIRS
来指定头文件路径。• Android.bp 使用
local_include_dirs
和export_include_dirs
来表示头文件路径,语法更简洁和结构化。
3. 库依赖
• Android.mk 中使用
LOCAL_STATIC_LIBRARIES
和LOCAL_SHARED_LIBRARIES
来依赖静态和动态库。• Android.bp 使用
static_libs
和shared_libs
指定静态和动态库,语义更加明确。
4. 安装路径
• Android.mk 使用
LOCAL_VENDOR_MODULE
、LOCAL_ODM_MODULE
等关键字指定模块的安装分区。• Android.bp 使用
vendor: true
、product_specific: true
等属性,来定义模块的安装位置,语法更直观。
5. 编译参数
• Android.mk 通过
LOCAL_CFLAGS
和LOCAL_CPPFLAGS
设置 C 和 C++ 编译选项。• Android.bp 则使用
cflags
和cppflags
来设置编译参数,结构清晰且易读。
总的来说,Android.mk 更加灵活但复杂,而 Android.bp 更加结构化和易于解析,是 Google 为提升构建效率和代码可维护性而设计的新配置语言。
在 Android.bp 文件中引入 APK、AAR、JAR 和 SO 库的编译方法
在AOSP开发中,合理引入和编译外部资源文件如APK
、AAR
、JAR
和SO
库至关重要。本文接下来将进一步阐述如何在Android.bp文件中正确引入这些资源,并探讨每个资源的最佳实践。
1. 引入 APK 文件
在项目中引入外部APK文件,例如需要将已经编译好的VoiceAI.apk
引入到构建系统中,可以使用android_app_import
模块。以下是具体配置示例:
android_app_import {
name: "MyApk",
privileged: true,
certificate: "platform",
system_ext_specific: true,
dex_preopt: {
enabled: true,
},
arch: {
arm: {
apk: "outputs/MyApk.apk",
},
},
}
其中:
•
name
是模块名,定义为 "VoiceAI"。•
privileged
表示这个应用需要拥有系统权限。•
certificate
指定该APK签名证书,通常使用 "platform"。•
dex_preopt
启用dex预优化,以提高性能。
2. 引入 AAR 文件
AAR库(Android Archive)包含了Android库项目的编译产物和资源文件,常见的第三方UI控件或SDK通常会以AAR的形式发布。下面以引入 lottie-2.8.0.aar
为例,演示如何将AAR文件集成到项目中。
android_library_import {
name: "lib-lottie",
aars: ["lottie-2.8.0.aar"],
sdk_version: "current",
}
接下来,在android_app
模块中,将其添加到static_libs
中:
android_app {
name: "LiveTv",
srcs: ["src/**/*.java"],
static_libs: ["lib-lottie"],
resource_dirs: ["res", "res_ext", "res-lottie"],
aaptflags: ["--extra-packages", "com.airbnb.lottie"],
}
注意:如果AAR中包含资源文件,需要手动解压并将资源文件放入项目的res
目录,并通过resource_dirs
引用这些资源。aaptflags
用于生成对应的资源类R
文件。
3. 引入 JAR 文件
引入JAR文件比AAR文件更加简单。以下是以引入opencv.jar
为例的配置方法:
java_import {
name: "face-opencv-jar",
jars: ["opencv.jar"],
sdk_version: "current",
}
为了使opencv.jar
库生效,我们需要在android_app
模块中进行关联:
android_app {
name: "LiveTv",
libs: [
"telephony-common",
"mediatek-framework",
"ims-common",
"face-opencv-jar", // 关联 opencv.jar
],
}
此配置将opencv.jar
文件引入到LiveTv
应用程序的构建中,使得应用可以使用该JAR中的所有功能和类。
注意:
• 如果遇到编译报错,类似于“某些库类丢失”的错误信息,可以尝试将
face-opencv-jar
移动到libs
列表或static_libs
中,避免冲突。
4. 引入 SO 文件
对于引入so
(共享库)文件,通常需要将其放在与架构(如armeabi
、armeabi-v7a
等)对应的目录中。下面以libjniopencv_face.so
为例:
cc_prebuilt_library_shared {
name: "libjniopencv_face",
arch: {
arm: {
srcs: ["armeabi/libjniopencv_face.so"],
},
arm64: {
srcs: ["armeabi/libjniopencv_face.so"],
},
},
}
将此库关联到应用的JNI库中:
android_app {
name: "LiveTv",
jni_libs: [
"libjniopencv_face",
],
}
这样就能将libjniopencv_face.so
动态链接库正确加载到LiveTv
应用中,并保证它在设备上运行时能找到并使用。
5. 综合使用 AAR、JAR 和 SO 文件的完整 Android.bp 配置
以下是一个完整的示例,它展示了如何在项目中同时引入AAR、JAR和SO库:
android_app {
name: "LiveTv",
srcs: ["src/**/*.java"],
sdk_version: "system_current",
min_sdk_version: "23", // 指定最低支持的SDK版本
resource_dirs: [
"res",
"material_res",
"res-lottie", // 引入lottie资源文件
],
libs: [
"face-opencv-jar", // 引入JAR文件
],
static_libs: [
"android-support-compat",
"android-support-core-ui",
"androidx.tvprovider_tvprovider",
"lib-lottie", // 引入AAR库
],
jni_libs: [
"libjniopencv_face", // 引入SO文件
],
javacflags: [
"-Xlint:deprecation", // 忽略弃用警告
"-Xlint:unchecked", // 忽略未检查类型的警告
],
aaptflags: [
"--version-name",
version_name, // 版本名
"--version-code",
version_code, // 版本号
"--extra-packages",
"com.airbnb.lottie", // 生成对应的R文件
],
}
这个配置包括了srcs
、resource_dirs
、static_libs
、jni_libs
等关键部分,确保项目能够正确引用不同类型的库文件,并且可以顺利编译运行。
6. 自动生成 SO 库配置脚本
在面对包含大量SO
库的情况时,逐个手动配置会非常耗时。为了解决这个问题,可以编写一个简单的脚本来自动生成相应的配置。以下是一个示例代码,用于遍历指定目录下的SO文件,并生成Android.bp
文件所需的JSON格式的内容:
private void getSoJson() {
String fileAbsolutePath = Environment.getExternalStorageDirectory().getPath() + "/Android/armeabi/";
File file = new File(fileAbsolutePath);
File[] subFile = file.listFiles();
String json = "";
String soName = "";
for (int i = 0; i < subFile.length; i++) {
if (!subFile[i].isDirectory()) {
String filename = subFile[i].getName();
String name = filename.split("\\.")[0];
json += "cc_prebuilt_library_shared {\n" +
" name: \"" + name + "\",\n" +
" arch: {\n" +
" arm: {\n" +
" srcs: [\"armeabi/" + filename + "\"],\n" +
" },\n" +
" arm64: {\n" +
" srcs: [\"armeabi/" + filename + "\"],\n" +
" },\n" +
" },\n" +
"}\n";
soName += "\"" + name + "\",\n";
}
}
write2File("so.txt", json);
write2File("libs.txt", soName);
}
这段代码将遍历指定路径下的SO文件,并生成适合Android.bp
文件的配置格式。每个so
库都会生成相应的cc_prebuilt_library_shared
模块。
通过以上对Android.bp文件语法的详细介绍,开发者可以灵活地在AOSP项目中引入各种外部资源文件,如APK
、AAR
、JAR
和SO
库。这不仅能有效提升项目的开发效率,还能确保不同的资源文件在项目构建时顺利集成和使用。此外,自动化生成SO库的配置也是一个非常有用的小技巧,能够大幅减少人工配置的时间成本。
结语
Android.bp文件的引入极大地提升了AOSP项目的可扩展性和可维护性。通过Soong系统的模块化管理,开发者能够更加灵活地定义和管理构建过程。本文详细介绍了Android.bp文件的基本结构、变量与属性、模块类型、依赖管理、条件编译、自定义构建步骤等关键内容。通过有效掌握这些知识,开发者可以更好地在AOSP项目中管理复杂的模块依赖,并优化构建效率。