Physical Address:
ChongQing,China.
WebSite:
自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代码来实现同样的效果,不过在我看来这种方式不够优雅,我们应尽可能在不做侵入式修改的前提下来实现我们的目的。