Android.bp中支持条件编译

欢迎大家关注我的微信公众号:Xndroid,探索Android技术,解锁移动世界。

自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,更多内容请点击直达~

<think>我们正在讨论的是在Android.bp文件中针对不同CPU架构的条件编译。根据引用[4],Android支持多种CPU架构,如ARMv5、ARMv7、x86、MIPS、ARMv8、MIPS64和x86_64。在构建过程中,我们可能需要为不同的架构指定不同的源文件、编译选项等。 在Android.bp中,条件编译主要通过以下两种方式实现: 1. 使用`arch`字段进行架构特定设置 2. 使用`target`字段(针对特定设备或产品) 重点介绍`arch`字段的使用。`arch`字段是一个字典,其键为架构名称(如"arm"、"arm64"、"x86"、"x86_64"等),值为该架构下的特定属性设置。 下面是一个示例,展示如何为不同架构设置不同的源文件和编译选项: ```python cc_library { name: "libexample", srcs: ["common.cpp"], arch: { arm: { srcs: ["arm_specific.cpp"], cflags: ["-DARM_OPTIMIZATION"], }, arm64: { srcs: ["arm64_specific.cpp"], cflags: ["-DARM64_OPTIMIZATION"], }, x86: { srcs: ["x86_specific.cpp"], cflags: ["-DX86_OPTIMIZATION"], }, x86_64: { srcs: ["x86_64_specific.cpp"], cflags: ["-DX86_64_OPTIMIZATION"], }, }, } ``` 在此示例中: - `common.cpp`是通用源文件,会被所有架构编译。 - 对于ARM架构(32位),额外编译`arm_specific.cpp`并定义了宏`ARM_OPTIMIZATION`。 - 对于ARM64架构,额外编译`arm64_specific.cpp`并定义了宏`ARM64_OPTIMIZATION`。 - 类似地,为x86和x86_64架构指定了特定的源文件和编译选项。 注意:`arch`字段还可以嵌套使用其他架构相关的属性,如`cflags`、`ldflags`、`include_dirs`等。 此外,我们还可以使用`target`字段来针对特定的设备或产品进行条件设置。例如: ```python cc_library { name: "libexample", target: { android: { srcs: ["android_specific.cpp"], }, host: { srcs: ["host_specific.cpp"], }, }, } ``` 对于多架构的构建,Android构建系统(Soong)会自动为每一个支持的架构生成构建规则。在构建时,可以通过环境变量`TARGET_PRODUCT`和`TARGET_BUILD_VARIANT`等指定构建目标。 引用[5]提到,可以使用不同的工具链来处理不同架构的构建,例如在FFmpeg构建中,通过检测NEON支持来加载相应的库。在Android.bp中,我们可以通过架构条件来启用特定的特性: ```python cc_library { name: "libffmpeg", arch: { arm: { cflags: ["-DHAVE_NEON=0"], srcs: ["ffmpeg_arm.c"], }, arm64: { cflags: ["-DHAVE_NEON=1"], srcs: ["ffmpeg_arm64.c", "neon_utils.c"], }, }, } ``` 在这个例子中,针对ARM64架构,我们定义了`HAVE_NEON`宏为1,并添加了NEON相关的源文件。 总结: 1. 使用`arch`字段按CPU架构进行条件编译。 2. 在`arch`的每个分支中,可以设置特定架构的源文件、编译选项等。 3. 可以使用`target`字段针对主机或Android设备进行条件设置。 注意事项: - 在条件编译中,应尽量减少重复代码,将通用部分放在顶层属性中。 - 架构名称使用小写,如"arm"、"arm64"、"x86"、"x86_64"等。 相关问题: </think>在 Android.bp 中针对不同 CPU 架构实现条件编译,主要通过以下核心机制实现: ### 一、架构特定属性声明 使用 `arch` 嵌套块针对不同架构设置专属属性: ```python cc_library { name: "libffmpeg", srcs: ["common.c"], arch: { arm: { srcs: ["arm/neon.c"], // ARMv7专用源文件 cflags: ["-mfloat-abi=softfp -mfpu=neon"], }, arm64: { srcs: ["arm64/simd.c"], // ARMv8专用源文件 cflags: ["-march=armv8-a"], }, x86: { srcs: ["x86/sse.c"], // x86专用源文件 cflags: ["-msse4.2"], }, x86_64: { cflags: ["-mavx2"], // x86_64专用编译选项 } } } ``` - **架构识别关键字**:`arm` (32位), `arm64`, `x86`, `x86_64`, `mips`, `mips64` [^4] - **生效规则**:构建系统自动匹配当前构建目标的 CPU 架构 ### 二、多版本库支持 通过 `compile_multilib` 配置库的生成策略: ```python cc_library { name: "libcodec", compile_multilib: "both", // 同时生成32位和64位版本 target: { android: { cflags: ["-DANDROID_PLATFORM"], }, linux_glibc: { cflags: ["-DLINUX_ENV"], } } } ``` 可选值: - `"32"`:仅生成32位库 - `"64"`:仅生成64位库 - `"both"`:同时生成32/64位库 - `"first"`:根据设备首选架构生成 ### 三、条件编译宏控制 结合 `cflags` 传递架构宏定义: ```python cc_binary { name: "media_process", cflags: [ "-DARCH_$($(TARGET_ARCH))", // 注入当前架构宏 "-DCPU_FEATURES_ENABLED" ], arch: { arm: { cflags: ["-DARM_NEON_SUPPORT"], // ARM专属宏 } } } ``` 在代码中通过宏判断: ```c #if defined(__aarch64__) // ARM64优化代码 #elif defined(__x86_64__) // x86_64优化代码 #endif #if defined(ARM_NEON_SUPPORT) // NEON指令集加速 #endif ``` ### 四、架构特定依赖 实现架构专属库依赖: ```python cc_library { name: "libmain", shared_libs: ["libbase"], arch: { arm64: { shared_libs: ["libneon_optimized"], // 仅ARM64链接此库 } } } ``` ### 五、文件级条件编译 针对不同架构指定专属源文件: ```python cc_library { name: "libvideo", srcs: [ "common.c", "generic.c", ], arch: { arm: { srcs: ["arm/asm_utils.S"], // ARM汇编文件 }, x86: { srcs: ["x86/simd_intrinsics.c"], } } } ``` ### 验证方法 查看构建产物确认架构差异化: ```bash # 检查ARMv7输出 m libffmpeg --target=armeabi-v7a ls out/target/product/*/obj/SHARED_LIBRARIES/libffmpeg_intermediates/arm # 检查ARMv8输出 m libffmpeg --target=arm64-v8a ls out/target/product/*/obj/SHARED_LIBRARIES/libffmpeg_intermediates/arm64 ``` > **性能提示**:条件编译应聚焦热点代码,避免过度碎片化增加维护成本。对于SIMD优化,推荐运行时检测替代静态分支(如ARM NEON)[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值