Android.bp中支持条件编译

Android.bp中更为优雅地实现条件编译。
Views: 98
4 0
Read Time:2 Minute, 49 Second

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

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

文章: 85

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注

zh_CNCN