构建条件链
本文面向已经读过 Android.bp属性链、Soong构建链 和 Soong模块类型 的读者。你需要知道 BP 属性会进入 Go struct、模块会产生 variant,但不必先熟悉全部产品变量。
本文回答:一个产品、架构或配置条件如何改变模块属性,最终如何变成编译器 -D 或源码中的 #if 分支。关键是区分四个对象:Blueprint select 是可配置属性表达式;arch/target 是 variant 属性合并;product_variables 与 soong_config 从产品配置取值;C/C++ 预处理器只消费最终 compiler command。本文不展开产品 makefile 的完整继承,那属于 产品配置链。
读完后,读者应能从一个 cflags: select(...) 或 arch.arm64.cflags 追到 Configurable.GetOrDefault、arch/variable mutator 和 baseCompiler.compilerFlags,再确认宏进入哪条编译命令;也能判断条件缺省、无匹配 case、错误类型和宏未定义分别发生在哪个阶段。
1. 四个阶段
条件不会从产品变量直接“跳进”C 源码。配置系统先提供值,Soong evaluator 或 mutator 选择并合并属性,模块 consumer 生成 cflags,Clang 预处理器最后决定 #if 分支。任何一层缺失都会得到不同现象。
| 条件机制 | 状态所有者 | 生效阶段 | 直接消费者 |
|---|---|---|---|
select() | Configurable[T] | 属性读取时 | 模块 action |
arch/target | ModuleBase variant | arch mutator | 顶层 property struct |
product_variables | ProductVariables + variable mutator | pre-deps mutator | 模块属性 |
soong_config | config namespace/module factory | load/property 应用 | 包装模块属性 |
#if/#ifdef | Clang preprocessor | compile action 执行时 | translation unit |
2. Select求值
2.1 状态结构
Blueprint 的 Configurable[T] 保存 condition、case、scope 和可能串接的 property value。它不是解析时立即得到的普通 list;属性 owner 在模块 action 中调用 Get 或 GetOrDefault 时,才用当前 evaluator 求最终值。
源码文件:build/blueprint/proptools/configurable.go
type Configurable[T ConfigurableElements] struct {
marker configurableMarker
propertyName string
inner *configurableInner[T]
postProcessors *[][]postProcessor[T]
}
type singleConfigurable[T ConfigurableElements] struct {
conditions []ConfigurableCondition
cases []ConfigurableCase[T]
scope *parser.Scope
}这解释了为什么 BP parser 通过不等于条件已经生效:parser 只保存 case;module variant、release flag 或 config variable 的实际值必须由 evaluator 提供。
2.2 读取屏障
GetOrDefault 调用 evaluate,匹配失败或类型错误会通过 PropertyErrorf 绑定到当前属性,并回落为空结果;只有 unset 才使用调用者传入的 default。错误不是悄悄选择 default。
源码文件:build/blueprint/proptools/configurable.go
相关函数/类型:Configurable.GetOrDefault
func (c *Configurable[T]) GetOrDefault(
evaluator ConfigurableEvaluator, defaultValue T) T {
result, err := c.evaluate(c.propertyName, evaluator)
if err != nil {
evaluator.PropertyErrorf(c.propertyName, "%s", err.Error())
result = nil
}
if result != nil {
return copyConfiguredValue(*result)
}
return defaultValue
}conditions_default、default 和“属性未设置”不是同一个状态。阅读一个 select 时应列出 condition 值域、case、default 和 unset 后模块 consumer 的默认值。
3. 架构合并
3.1 Variant先行
arch 与 target 属性只对带 android:"arch_variant" tag 的字段生成 variant property struct。arch transition 先决定当前模块的 Arch、Os 和 image,之后 setArchProperties 才选择匹配 shard 并合并进顶层属性。
源码文件:build/soong/android/arch.go
相关函数/类型:ModuleBase.setArchProperties
func (m *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) {
arch := m.Arch()
os := m.Os()
for i := range m.archProperties {
genProps := m.GetProperties()[i]
if m.archProperties[i] == nil {
continue
}
var propStructs []reflect.Value
for _, archProperty := range m.archProperties[i] {
shard := getArchProperties(ctx, archProperty, arch, os,
m.Target().NativeBridge == NativeBridgeEnabled)
propStructs = append(propStructs, shard...)
}
for _, propStruct := range propStructs {
mergePropertyStruct(ctx, genProps, propStruct)
}
}
}arch.arm64 并不是在一个 common module 上临时 if;它只会合并到 arm64 variant。依赖者请求 x86_64 variant 时会得到另一套 property state。
3.2 合并顺序
匹配集合可能包含 OS、arch、multilib、native bridge 和 target-specific shard。list 属性通常追加,scalar 属性按 proptools 合并规则覆盖;带 variant_prepend 的字段改变列表方向。不能仅凭 BP 中块的视觉顺序推断最终 flags。
4. 产品变量
4.1 反射入口
传统 product_variables 由 VariableMutator 读取 ModuleBase 动态生成的 Product_variables struct,再与 Config.productVariables 对照。变量没设置、bool 为 false 或模块没有对应属性时会跳过;命中后才把变量属性追加到模块顶层属性。
源码文件:build/soong/android/variable.go
相关函数/类型:VariableMutator
variableValues := reflect.ValueOf(a.variableProperties).
Elem().FieldByName("Product_variables")
productVariables := reflect.ValueOf(&mctx.Config().productVariables).Elem()
for i := 0; i < variableValues.NumField(); i++ {
variableValue := variableValues.Field(i)
name := variableValues.Type().Field(i).Name
val := productVariables.FieldByName(name)
if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() {
continue
}
val = val.Elem()
if val.Kind() == reflect.Bool && !val.Bool() {
continue
}
if variableValue.IsZero() {
continue
}
a.setVariableProperties(mctx, property, variableValue, val.Interface())
}4.2 格式替换
setVariableProperties 会先用变量值替换属性中的格式占位,再调用 AppendMatchingProperties 合并。因而 product variable 可以改变 string/list 内容;类型不匹配会产生 property error,而不是把任意值 stringify。
5. Soong配置
5.1 类型包装
soong_config_module_type 注册一个包装模块类型,它声明 base module type、namespace、variables 和可被条件修改的 properties。soong_config_bool_variable、string/value variable 是定义输入,不直接生成编译产物。
源码文件:build/soong/android/soong_config_modules.go
func RegisterSoongConfigModuleBuildComponents(ctx RegistrationContext) {
ctx.RegisterModuleType("soong_config_module_type_import",
SoongConfigModuleTypeImportFactory)
ctx.RegisterModuleType("soong_config_module_type",
SoongConfigModuleTypeFactory)
ctx.RegisterModuleType("soong_config_string_variable",
SoongConfigStringVariableDummyFactory)
ctx.RegisterModuleType("soong_config_bool_variable",
SoongConfigBoolVariableDummyFactory)
ctx.RegisterModuleType("soong_config_value_variable",
SoongConfigValueVariableDummyFactory)
}5.2 缺省条件
conditions_default 在 bool 未设置/false、string 未设置或值不在声明集合等条件下生效;如果某个具体 case 显式写空对象,则表示该值不应用 default。空 case 和缺 case 的语义不同。
5.3 配置所有者
namespace/value 通常从产品 Make 配置进入 Soong config。模块作者应确认谁设置值、何时写入、允许值域和 default,而不是在 BP 中假设某个 vendor 变量总存在。未导入 module type 或 property 不在 allowlist 会在 load/property 阶段失败。
6. Flags消费
6.1 最终读取
cc 的 baseCompiler.compilerFlags 是条件属性到编译命令的关键消费者。它对 Cflags、Cppflags、Srcs 等 Configurable 调用 GetOrDefault(ctx, nil);此时 select 已按当前 variant 求值,arch/product properties 也已经合并。
源码文件:build/soong/cc/compiler.go
相关函数/类型:baseCompiler.compilerFlags
cflags := compiler.Properties.Cflags.GetOrDefault(ctx, nil)
cppflags := compiler.Properties.Cppflags.GetOrDefault(ctx, nil)
vendorcflags := compiler.Properties.Target.Vendor.Cflags.GetOrDefault(ctx, nil)
productcflags := compiler.Properties.Target.Product.Cflags.GetOrDefault(ctx, nil)
CheckBadCompilerFlags(ctx, "cflags", cflags)
CheckBadCompilerFlags(ctx, "cppflags", cppflags)
esc := proptools.NinjaAndShellEscapeList
flags.Local.CFlags = append(flags.Local.CFlags, esc(cflags)...)
flags.Local.CppFlags = append(flags.Local.CppFlags, esc(cppflags)...)6.2 分区附加
同一个 module type 的 vendor/product/recovery variant 会在 consumer 中追加各自 Target.*.Cflags。ctx.inVendor() 等判断读取的是当前 variant state,不是运行设备上的动态分区状态。
源码文件:build/soong/cc/compiler.go
if ctx.inVendor() {
flags.Local.CFlags = append(flags.Local.CFlags, esc(vendorcflags)...)
}
if ctx.inProduct() {
flags.Local.CFlags = append(flags.Local.CFlags, esc(productcflags)...)
}
if ctx.inRecovery() {
flags.Local.CFlags = append(flags.Local.CFlags,
esc(compiler.Properties.Target.Recovery.Cflags)...)
}6.3 宏注入
当最终 cflags 含 -DFEATURE_X=1 时,Ninja action 把它传给 Clang。Soong 不解释 FEATURE_X 的 C 语义;预处理器才根据 token 决定条件分支。若 BP 条件选择正确但源文件没包含相关代码或宏拼写不同,构建可能成功却没有预期功能。
7. 预处理器
7.1 固有宏
部分宏来自头文件或编译器,而非 BP -D。Bionic 的 sys/cdefs.h 固定定义 __BIONIC__,__LP64__ 由编译 target 决定,_FORTIFY_SOURCE 则通常来自构建 flags;三者 owner 不同。
源码文件:bionic/libc/include/sys/cdefs.h
#define __BIONIC__ 1
#if defined(__LP64__)
#define __WORDSIZE 64
#else
#define __WORDSIZE 32
#endif
#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 0
#if !defined(__clang_analyzer__)
#define __BIONIC_FORTIFY 1
#endif
#endif7.2 分支结果
预处理发生在每个 translation unit。相同源文件若属于两个 variant,可能收到不同的 -D、target triple 和内建宏,从而生成不同对象文件。这是“条件编译”真正删除或保留代码的阶段。
8. 失败分流
8.1 条件错误
select condition 类型不匹配、case 缺 default 或 evaluator 取值失败,会报 BP property error;此时不要查 C 源码,因为编译 action 尚未可靠生成。
8.2 合并错误
arch/product variable 属性未带 arch_variant、目标字段不存在或 property 类型不匹配,会在 mutator/merge 阶段失败。看到某 variant 缺 flags 时,先确认它是否实际存在和 enabled。
8.3 Flag错误
CheckBadCompilerFlags 会拒绝不允许的参数;Ninja/shell escaping 又可能暴露错误引号。应查看最终 rule args,而不是只读 BP 文本。
8.4 宏错误
编译成功但功能分支未生效时,检查 compile command 中是否真的有 -D、宏值是 1 还是仅定义、源码使用 #ifdef 还是 #if FEATURE,以及内建 target 宏是否与预期一致。
9. 验证路径
用源码搜索重新走一遍主链:
rg -n "type Configurable|GetOrDefault|func .*evaluate" build/blueprint/proptools
rg -n "setArchProperties|VariableMutator|setVariableProperties" build/soong/android
rg -n "compilerFlags|GetOrDefault.*Cflags|Target.Vendor.Cflags" build/soong/cc测试边界应分层:Blueprint configurable 测试给定 condition/case 并断言结果或 property error;Soong arch/variable 测试给定 BP 和 TestConfig 并断言 variant 属性;cc 测试读取具体 variant 的 compile rule args。它们能证明属性到 flag,不证明运行时功能。
最后选一个真实模块,记录 condition 输入、匹配 case、variant 名、最终 cflags 和一处 #if。如果五项无法逐一定位,就还没有证明条件真正生效。
10. 边界收束
构建条件是一条跨所有者的数据链,而不是三种语法的清单。select 延迟属性求值,arch/product mutator 合并 variant 状态,cc consumer 生成 compiler flags,Clang 才裁剪源码。调试必须从失败阶段向上下游检查,不能看到 #ifdef 就假设产品变量直接控制它。
