Framework | 深入理解AOSP中的Android.bp语法

科技   2024-09-10 16:48   浙江  

深入理解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. 1. cc_library: 用于构建C/C++库,可以是静态库或动态库。

  2. 2. java_library: 用于构建Java库。

  3. 3. android_library: 专门用于Android开发的Java库。

  4. 4. cc_binary: 用于构建C/C++可执行文件。

  5. 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_libslibs属性指定了各自依赖的库文件。

依赖管理

在大型项目中,模块间的依赖管理至关重要。Android.bp提供了多种方式来管理模块之间的依赖。

  • • static_libs: 用于指定静态链接库依赖。

  • • shared_libs: 用于指定动态链接库依赖。

  • • deps: 用于一般性依赖,适用于Java和其他模块类型。

举例来说,如果一个模块依赖其他多个库:

cc_library {
    name: "libfoo",
    srcs: ["foo.cpp"],
    static_libs: ["libbar""libbaz"],
}

在这个例子中,libfoo会静态链接libbarlibbaz库。

条件编译

Android.bp文件允许根据不同的目标设备或架构进行条件编译。通过targetarch字段,可以为不同的设备架构或操作系统版本配置不同的编译选项。

示例:为不同架构设置编译选项

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. 1. 未定义的模块: 当某个模块引用了不存在的依赖模块时,Soong会报错。解决方案是检查依赖关系并确保所有依赖模块都已正确定义。

  2. 2. 模块属性不匹配: 不同模块类型有各自特定的属性。如果在cc_library中使用了java_library的属性,会导致解析错误。开发者需要仔细检查每个模块类型的属性文档。

  3. 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_MODULELOCAL_ODM_MODULE 等关键字指定模块的安装分区。

  • • Android.bp 使用 vendor: trueproduct_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开发中,合理引入和编译外部资源文件如APKAARJARSO库至关重要。本文接下来将进一步阐述如何在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(共享库)文件,通常需要将其放在与架构(如armeabiarmeabi-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文件
    ],
}

这个配置包括了srcsresource_dirsstatic_libsjni_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项目中引入各种外部资源文件,如APKAARJARSO库。这不仅能有效提升项目的开发效率,还能确保不同的资源文件在项目构建时顺利集成和使用。此外,自动化生成SO库的配置也是一个非常有用的小技巧,能够大幅减少人工配置的时间成本。

结语

Android.bp文件的引入极大地提升了AOSP项目的可扩展性和可维护性。通过Soong系统的模块化管理,开发者能够更加灵活地定义和管理构建过程。本文详细介绍了Android.bp文件的基本结构、变量与属性、模块类型、依赖管理、条件编译、自定义构建步骤等关键内容。通过有效掌握这些知识,开发者可以更好地在AOSP项目中管理复杂的模块依赖,并优化构建效率。


虎哥Lovedroid
Android技术达人 近10年一线开发经验 关注并分享Android、Kotlin新技术,新框架 多年Android底层框架修改经验,对Framework、Server、Binder等架构有深入理解
 最新文章