IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Android.bp中支持条件编译

    FranzKafka95发表于 2024-02-02 15:10:31
    love 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
    0 0 %
    Sad
    Sad
    0 0 %
    Excited
    Excited
    0 0 %
    Sleepy
    Sleepy
    0 0 %
    Angry
    Angry
    0 0 %
    Surprise
    Surprise
    0 0 %

    The post Android.bp中支持条件编译 first appeared on FranzKafka Blog.



沪ICP备19023445号-2号
友情链接