随着Android版本的迭代,越来越多的移动终端都用上了香喷喷的Android 9和10系统。相对于原来的Android版本,Android 9和10越来越多的使用Android.bp替换以前的Android.mk编译脚本。我们知道 Android.mk采用Makefile的语言,所以为了控制条件编译和多版本适配,我们可以在不同产品直接在Android.mk中采用Makefile语言控制编译。虽然我们也可以通过Android的预置工具androidmk将Android.mk转换为Android.bp脚本,但是对于有宏控制的条件编译,androidmk是无能为力的。这是因为Android.bp是类似JSON的纯文本形式,对于Android.mk里面流控制部分,在Android.bp里要借助使用go语言文件去进行控制。好了说了这么多了,我想读者应该知道这个篇章要写什么了,那就是手把手带领大伙手撸一把Android.bp添加宏控制编译。
我们知道在Android原来的编译脚本Android.mk中添加宏控制有两种场景,分别是:
#无条件控制宏编译
LOCAL_CFLAGS += -Wno-error=implicit-function-declaration -DPRINT
#有条件的添加宏控制
ifeq ($(ANDROIDBP_FUN), "YES")
LOCAL_CFLAGS += -DXXX
endif
下面的篇章我们将分别从上述两种场景出发,来说明在Android.bp中如何添加宏控制。
这里我会以实际案例来说明,怎么一步步添加无条件控制的宏。
#include
int main()
{
#ifdef PRINT //宏控制
printf("Hello world\n");
#endif
#ifdef XXX //宏控制
printf("XXX\n");
#endif
printf("This is AndroidBp Test\n");
return 0;
}
代码非常简单,就是一个简单的打印。在我们正式在Android.bp中添加宏前,我们先看看Android.mk应该怎么编写。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := AndroidBp
LOCAL_SRC_FILES := main.c
ANDROIDBP_FUN=YES
#有条件的添加宏控制
ifeq ($(ANDROIDBP_FUN), "YES")
LOCAL_CFLAGS += -DXXX
endif
#无条件宏控制
LOCAL_CFLAGS += -Wno-error=implicit-function-declaration -DPRINT
LOCAL_SHARED_LIBRARIES := libcutils liblog libutils
include $(BUILD_EXECUTABLE)
这个也非常简单,就是编译一个可执行文件,并有条件和无条件的添加宏控制。
在前面的博客中,我们知道Android内置的androidmk工具能将Android.mk转换为对应的Android.bp,但是当Android.mk中有宏条件控制时就无能为力了。下面让我们转换一下看看是否如此:
[SPRD] xxx@pd:~/ssd/xxx/ap/idh.code/xxxxroid/external/AndroidBp$ androidmk Android.mk > Android.bp
[SPRD] xxx@pd:~/ssd/xxx/ap/idh.code/xxxxroid/external/AndroidBp$
查看转换后生成的Android.bp文件如下,可以看到androidmk的转换失效了,
ANDROIDBP_FUN = ["YES"]
cc_binary {
name: "AndroidBp",
srcs: ["main.c"],
// ANDROIDMK TRANSLATION ERROR: unsupported conditional
// ifeq ($(ANDROIDBP_FUN), "YES")
cflags: ["-DXXX"] + [ // ANDROIDMK TRANSLATION ERROR: endif from unsupported contitional
// endif
"-Wno-error=implicit-function-declaration",
"-DPRINT",
],
shared_libs: [
"libcutils",
"liblog",
"libutils",
],
}
通过前面的章节我们可以直接,Android.bp直接添加宏非常简单,只需要在cflags后面添加对应的宏就OK了。
cflags: ["-DXXX"] + [ // ANDROIDMK TRANSLATION ERROR: endif from unsupported contitional
// endif
"-Wno-error=implicit-function-declaration",
"-DPRINT",
],
根据前面的章节我们知道,在Android.bp中需要有条件的添加宏是不能直接做到的,必须借助go脚本实现动态控制编译项,那么让我们看看怎么动态添加如下的宏:
#有条件的添加宏控制
ifeq ($(ANDROIDBP_FUN), "YES")
LOCAL_CFLAGS += -DXXX
endif
我们在Android.bp同级目录添加go脚本文件xxxparser.go,其内容如下:
package xxxparser
import (
"android/soong/android"
"android/soong/cc"
)
func init() {
// resister a module "xxxparser_defaults"
android.RegisterModuleType("xxxparser_defaults", xxxdroidDefaultsFactory)
}
func xxxdroidDefaultsFactory() (android.Module) {
module := cc.DefaultsFactory()
android.AddLoadHook(module, xxxdroidDefaults)
return module
}
func xxxdroidDefaults(ctx android.LoadHookContext) {
type props struct {
Cflags []string
}
p := &props{}
p.Cflags = globalDefaults(ctx)
ctx.AppendProperties(p)
}
func globalDefaults(ctx android.BaseContext) ([]string) {
var cppflags []string
if ctx.AConfig().Getenv("ANDROIDBP_FUN") == "YES" {
cppflags = append(cppflags,"-DXXX")
}
return cppflags
}
在Android.bp开头位置引入go脚本文件xxxparser.go,如下:
//引入go脚本
bootstrap_go_package {
name: "soong-xxxparser",
pkgPath: "android/soong/xxxparser",
deps: [
"blueprint",
"blueprint-pathtools",
"soong",
"soong-android",
"soong-cc",
"soong-genrule",
],
srcs: [
"xxxparser.go",
],
pluginFor: ["soong_build"],
}
xxxparser_defaults {
name: "xxxparser_defaults",
}
ANDROIDBP_FUN = ["YES"]
cc_binary {
defaults: ["xxxparser_defaults"],
name: "AndroidBp",
srcs: ["main.c"],
cflags: ["-Wno-error=implicit-function-declaration"],
shared_libs: [
"libcutils",
"liblog",
"libutils",
],
}
以上语句可以保证运行Android.bp时,先编译对应的xxxparser.go运行go脚本时,会首先运行init函数,将 xxxdroidDefaultsFactory函数注册到module中,之后调用xxxdroidDefaultsFactory函数时,会将回调函数 xxxdroidDefaults注册进去之后调用 privateParserDefaults 时,我们可以从 ctx.AConfig() 中获取好多属性
(参考 build/soong/android/config.go 中对 build/soong/android/module.go中的 androidBaseContext interface的各种函数实现),其中有一项是获取宏值的,之后回调xxxdroidDefaults添加宏信息。
在正式进行编译之前,让我们对编译环境设置环境变量,我这里使用的是ubuntu编译,所以这个不用多说怎么配置环境变量了,如下:
[SPRD] xxx@pd:~/ssd/xxx/ap/idh.code$ export ANDROIDBP_FUN=YES
[SPRD] xxx@pd:~/ssd/xxx/ap/idh.code$ echo $ANDROIDBP_FUN
YES
[SPRD] xxx@pd:~/ssd/xxx/ap/idh.code$
执行可执行文件,查看输出结果,完美。
1|xxx:/system/bin # ./AndroidBp
XXX
This is AndroidBp Test
xxx:/system/bin #
由于对go语法不是非常了解,这里只对xxxparser.go中用到的语法进行简单的解析,以便大家了解,如果想深入的话得自行研究内功了。这里臣妾做不到。
我们知道bin文件执行先从main开始,而运行go语言时,先运行init函数,我们这里的此函数为:
func init() {
// resister a module "xxxparser_defaults"
// 注册xxxparser_defaults, 指定要调用的方法入口xxxdroidDefaultsFactory
android.RegisterModuleType("xxxparser_defaults", xxxdroidDefaultsFactory)
}
脚本开头import了两个包 “android/soong/android” 和 “android/soong/cc”从 build/soong/ 目录下搜索 “func RegisterModuleType” 来获取RegisterModuleType函数的定义位置RegisterModuleType函数位于build/soong/android/register.go中,如下:
func RegisterModuleType(name string, factory ModuleFactory) {
moduleTypes = append(moduleTypes, moduleType{name, ModuleFactoryAdaptor(factory)})
}
可以看出将 name 和 ModuleFactoryAdaptor(factory) append到 var moduleTypes []moduleType 中
moduleType类型如下:
type moduleType struct {
name string
factory blueprint.ModuleFactory
}
xxxparser.go 开头 import “github.com/google/blueprint”我们从github上下载 github.com/google/blueprint/ 仓后,搜索 “type ModuleFactory” 来获取 ModuleFactory 定义位置,位于 github.com/buleprint/context.go中,如下:
// A ModuleFactory function creates a new Module object. See the
// Context.RegisterModuleType method for details about how a registered
// ModuleFactory is used by a Context.
type ModuleFactory func() (m Module, propertyStructs []interface{})
可以看出ModuleFactory为一函数指针,形参为null,返回类型为 Module, []interface{}。
注: 此处我们详细的讲解了如何搜索变量名和函数名的位置,即关键字 “type xxx"和"func xxx”,之后不再给出详细步骤。
通过搜索如下所示:
// ModuleFactoryAdapter Wraps a ModuleFactory into a blueprint.ModuleFactory by converting an Module
// into a blueprint.Module and a list of property structs
func ModuleFactoryAdaptor(factory ModuleFactory) blueprint.ModuleFactory {
return func() (blueprint.Module, []interface{}) {
module := factory()
return module, module.GetProperties()
}
}
此处将xxxparser.go中的 type ModuleFactory func() Module 函数指针,转换成 blueprint.ModuleFactory 类型的函数指针。
从上文可以得知,xxxdroidDefaultsFactory类型为 type ModuleFactory func() Module
我们实现了这个函数为:
func xxxdroidDefaultsFactory() (android.Module) {
module := cc.DefaultsFactory()
android.AddLoadHook(module, xxxdroidDefaults)
return module
}
函数实现见 build/soong/cc/cc.go,如下:
func DefaultsFactory(props ...interface{}) android.Module {
module := &Defaults{}
module.AddProperties(props...)
module.AddProperties(
&BaseProperties{},
&VendorProperties{},
&BaseCompilerProperties{},
&BaseLinkerProperties{},
&LibraryProperties{},
&FlagExporterProperties{},
&BinaryLinkerProperties{},
&TestProperties{},
&TestBinaryProperties{},
&UnusedProperties{},
&StlProperties{},
&SanitizeProperties{},
&StripProperties{},
&InstallerProperties{},
&TidyProperties{},
&CoverageProperties{},
&SAbiProperties{},
&VndkProperties{},
<OProperties{},
&PgoProperties{},
&android.ProtoProperties{},
)
android.InitDefaultsModule(module)
return module
}
好了,就到这里了。不是本人赖,是因为这个我也是参考别人的。但是各位放心,后续我会继续将本章接力下去继续分析精进的。
Android.bp正确姿势添加宏控制编译指南中介绍的两种添加宏的场景就到这里了,我想通过这个文档大伙一定能构建自己的Android.bp脚本了。好了最后如果文章对你有帮助麻烦点个赞,如果觉得很low也可以拍个砖一起探讨。
参考文章:
go语言控制android.bp选择性编译