赞
踩
自Android P(Android 9.0)以来,安卓系统在构建时开始采用Blueprint描述文件用于指导编译构建,同时引入了新的编译系统soong;而原本的Kati+GNU Make的组合将会被逐渐被替代;原本使用的Android.mk编译配置文件将会被Android.bp替代。
使用Android.bp进行构建时经常会遇到一个问题就是如何在Android.bp中支持条件编译,这是因为Android.bp中对条件编译的支持是相当有限的。
比较常见的一个场景是,当我们的代码涉及到一些与平台、硬件或功能相关的特性时,往往会使用不同的源码文件或者说依赖不同的库,设定不同的编译宏等等。如果我们使用Android.mk,可以直接通过ifeq或者ifneq这类逻辑控制语句实现差异化配置,如下示例:
- ifeq ($(TARGET_ARCH),x86_64)
- LOCAL_SRC_FILES+=win64.cc
- else ifeq ($(TARGET_ARCH),i686)
- LOCAL_SRC_FILES+=win32.cc
- else
- LOCAL_SRC_FILES+=win64.cc
- endif
如果我们使用Android.bp,那么我们可以在Android.bp中通过arch字段进行配置从而实现与上述类似条件编译的效果。如下所示:
- /frameworks/libs/native_bridge_support/libOpenMAXAL/Android.bp
- cc_library {
- defaults: ["native_bridge_stub_library_defaults"],
- name: "libnative_bridge_guest_libOpenMAXAL",
- overrides: ["libOpenMAXAL"],
- stem: "libOpenMAXAL",
- arch: {
- arm: {
- srcs: ["stubs_arm.cc"],
- },
- arm64: {
- srcs: ["stubs_arm64.cc"],
- },
- },
- shared_libs: [
- "liblog",
- "libnative_bridge_guest_libnativewindow",
- ],
- }
在整个Android.bp中,我们可以看到通过arch字段配置了arm和arm64两个不同平台,使用了不同的源码文件,这样可以根据平台差异这个条件实现条件编译;
但如果我们的差异并不能简单根据平台来区分怎么办呢,在我们实际开发过程中,有很多差异化的配置可能是无法通过平台进行区分的,也无法通过xml,json配置或者编译宏等实现兼容,比如不同的功能特性导致需要链接不同的依赖库等。此时我们就只能借助soong的自身的能力来实现了。
网上关于这部分的指导其实还是比较少的,但其实soong提供了解决方案,即在Android.bp中通过soong_config_module_type来帮我们进行配置,可以让我们将差异化的配置以module的形式对外提供,在编译时进行引用。
我们首先看看soong_config_module_type的配置说明,在官方文档文档中, soong_config_module_type是一种机制,允许在 Android.bp构建文件中基于 Soong 配置变量来定义模块类型。一旦在 Android.bp文件中定义了这样的模块类型,它就可以被其他模块进行引用。
这种机制允许开发人员根据所需的条件、环境或特定的配置选项,定义定制的模块类型。这些条件可以基于 Soong 配置系统中的变量,例如目标架构、编译器选项、操作系统等等。一旦定义了这样的模块类型,它将在该 Android.bp文件中可用,并可以用于定义新的模块,以满足特定条件下的构建需求。
这里将进行举例说明:
- #声明一个soong编译配置模块
- #name表明该模块的名称
- #config_namespace表名该编译配置模块所属的命名空间,用于在Makefile中使用
- #modlue_type用于表明该编译配置模块所附属的编译配置
- #variables,bool_variables,value_variables表明该编译配置模块所支持的选项类型
- #bool_variables用于定义一个表征bool类型的配置项
- #value_variables用于定义一个可传递的配置项,通过%s进行获取
- #properties用于表明该编译配置模块最终影响的可选项,其来自于moudle_type中的可选项
- soong_config_module_type {
- name: "acme_cc_defaults",
- module_type: "cc_defaults",
- config_namespace: "acme",
- variables: ["board"],
- bool_variables: ["feature"],
- value_variables: ["width"],
- properties: ["cflags", "srcs"],
- }
-
- #这里针对名为board的配置项设定可选值
- soong_config_string_variable {
- name: "board",
- values: ["soc_a", "soc_b"],
- }
-
- #这里对soong_config_module_type声明的编译配置模块做详细定义
- acme_cc_defaults {
- #为该编译配置命名为acme_defaults
- name: "acme_defaults",
- cflags: ["-DGENERIC"],
- soong_config_variables: {
- board: {
- soc_a: {
- cflags: ["-DSOC_A"],
- },
- soc_b: {
- cflags: ["-DSOC_B"],
- },
- conditions_default: {
- cflags: ["-DSOC_DEFAULT"],
- },
- },
- feature: {
- cflags: ["-DFEATURE"],
- conditions_default: {
- cflags: ["-DFEATURE_DEFAULT"],
- },
- },
- width: {
- cflags: ["-DWIDTH=%s"],
- conditions_default: {
- cflags: ["-DWIDTH=DEFAULT"],
- },
- },
- },
- }
-
- cc_library {
- name: "libacme_foo",
- #这里使用名为acme_defaults的编译配置
- defaults: ["acme_defaults"],
- srcs: ["*.cpp"],
- }
这里我们来理解其整个定义过程,soong_config_module_type用于声明一个编译配置模块,其内部包含多个配置子项(variables/value_variables/bool_variables),我们可以将其类比于C/C++中自定义一个结构体,结构体内部含有多个子元素,其中name字段用于表征该配置模块的名称,可类比结构体类型名;之后我们以该结构体类型声明了一个结构体变量,并对其进行赋值,最终我们在cc_library中使用该结构体变量。
相信到这里大家还是蒙的,仅凭上述内容并没有看出是怎么进行条件编译的。其实在进行“结构体变量赋值时”,我们已经针对每一个子成员给出了多个“可选值”,怎样去选择这些可选值呢,这时候就需要Makefile的配合了,以上述定义为例,我们在我们的Makefile中进行使用:
- #在Makefile中进行使用,如在BoardConfig中添加
- #SOONG_CONFIG_NAMESPACES 用于索引对应namespace内的编译配置
- SOONG_CONFIG_NAMESPACES += acme
-
- #SOONG_CONFIG_${NAMESPACE}用于选则可配置的VARIABLE
- SOONG_CONFIG_acme += \
- board \
- feature \
-
- #SOONG_CONFIG_${NAMESPACE}_${VARIABLE}用于选择具体的值
- SOONG_CONFIG_acme_board := soc_a
- SOONG_CONFIG_acme_feature := true
- SOONG_CONFIG_acme_width := 200
最终我们编译libacme_foo时将会以cflags="-DGENERIC -DSOC_A -DFEATURE"的方式进行编译;如果我们在BoardConfig.mk添加的内容如下:
- #在Makefile中进行使用,如在BoardConfig中添加
- #SOONG_CONFIG_NAMESPACES 用于索引对应namespace内的编译配置
- SOONG_CONFIG_NAMESPACES += acme
-
- #SOONG_CONFIG_${NAMESPACE}用于选则可配置的VARIABLE
- SOONG_CONFIG_acme += \
- board \
- feature \
- width \
-
- #SOONG_CONFIG_${NAMESPACE}_${VARIABLE}用于选择具体的值
- SOONG_CONFIG_acme_board := soc_b
- SOONG_CONFIG_acme_feature := true
- SOONG_CONFIG_acme_width := 200
此时编译libacme_foo时将会以cflags="-DGENERIC -DSOC_B -DFEATURE -DWIDTH=200"的方式进行编译;如果我们需要添加不同的src,那么我们可以修改如下:
- acme_cc_defaults {
- #为该编译配置命名为acme_defaults
- name: "acme_defaults",
- cflags: ["-DGENERIC"],
- soong_config_variables: {
- board: {
- soc_a: {
- cflags: ["-DSOC_A"],
- srcs: ["SocA.cc"],
- },
- soc_b: {
- cflags: ["-DSOC_B"],
- srcs: ["SocB.cc"],
- },
- conditions_default: {
- cflags: ["-DSOC_DEFAULT"],
- },
- },
- feature: {
- cflags: ["-DFEATURE"],
- conditions_default: {
- cflags: ["-DFEATURE_DEFAULT"],
- },
- },
- width: {
- cflags: ["-DWIDTH=%s"],
- conditions_default: {
- cflags: ["-DWIDTH=DEFAULT"],
- },
- },
- },
- }
如果我们需要依赖不同的库,那么我们可以修改如下:
- #在properties中添加shared_libs
- soong_config_module_type {
- name: "acme_cc_defaults",
- module_type: "cc_defaults",
- config_namespace: "acme",
- variables: ["board"],
- bool_variables: ["feature"],
- value_variables: ["width"],
- properties: ["cflags", "srcs","shared_libs "],
- }
-
- #添加不同的依赖库
- acme_cc_defaults {
- #为该编译配置命名为acme_defaults
- name: "acme_defaults",
- cflags: ["-DGENERIC"],
- soong_config_variables: {
- board: {
- soc_a: {
- cflags: ["-DSOC_A"],
- shared_libs: ["libsoc_a"],
- },
- soc_b: {
- cflags: ["-DSOC_B"],
- shared_libs: ["libsoc_b"],
- },
- conditions_default: {
- cflags: ["-DSOC_DEFAULT"],
- },
- },
- feature: {
- cflags: ["-DFEATURE"],
- conditions_default: {
- cflags: ["-DFEATURE_DEFAULT"],
- },
- },
- width: {
- cflags: ["-DWIDTH=%s"],
- conditions_default: {
- cflags: ["-DWIDTH=DEFAULT"],
- },
- },
- },
- }
综上,我们可以看到在Google在引入Android.bp后,想要使用条件编译会变得较为复杂,需要结合Android.bp与Makefile,在两者的共同作用下才能达到我们的目的。当然,这篇文章中所讲的方法并不是唯一的方法,我们还可以通过修改Soong编译系统,编写Golang代码来实现同样的效果,不过在我看来这种方式不够优雅,我们应尽可能在不做侵入式修改的前提下来实现我们的目的。
原文首发于我的个人网站:https://coderfan.net,更多内容请点击直达~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。