Add conditions_default for soong config variables.

Each variable can specify a conditions_default for properties to be used
when the variable is not set, not set to a true value (for bools), or is
set to a value that is not present in the module (for strings).

Test: m nothing
Test: go test soong tests
Change-Id: I76ec026da2369b407f0f530f77760f530e7958fc
This commit is contained in:
Liz Kammer 2020-12-16 12:42:02 -08:00
parent 324234bd00
commit 432bd598ae
7 changed files with 468 additions and 116 deletions

View file

@ -430,14 +430,24 @@ soong_config_module_type {
soong_config_string_variable { soong_config_string_variable {
name: "board", name: "board",
values: ["soc_a", "soc_b"], values: ["soc_a", "soc_b", "soc_c"],
} }
``` ```
This example describes a new `acme_cc_defaults` module type that extends the This example describes a new `acme_cc_defaults` module type that extends the
`cc_defaults` module type, with three additional conditionals based on `cc_defaults` module type, with three additional conditionals based on
variables `board`, `feature` and `width`, which can affect properties `cflags` variables `board`, `feature` and `width`, which can affect properties `cflags`
and `srcs`. and `srcs`. Additionally, each conditional will contain a `conditions_default`
property can affect `cflags` and `srcs` in the following conditions:
* bool variable (e.g. `feature`): the variable is unspecified or not set to a true value
* value variable (e.g. `width`): the variable is unspecified
* string variable (e.g. `board`): the variable is unspecified or the variable is set to a string unused in the
given module. For example, with `board`, if the `board`
conditional contains the properties `soc_a` and `conditions_default`, when
board=soc_b, the `cflags` and `srcs` values under `conditions_default` will be
used. To specify that no properties should be amended for `soc_b`, you can set
`soc_b: {},`.
The values of the variables can be set from a product's `BoardConfig.mk` file: The values of the variables can be set from a product's `BoardConfig.mk` file:
``` ```
@ -445,6 +455,7 @@ SOONG_CONFIG_NAMESPACES += acme
SOONG_CONFIG_acme += \ SOONG_CONFIG_acme += \
board \ board \
feature \ feature \
width \
SOONG_CONFIG_acme_board := soc_a SOONG_CONFIG_acme_board := soc_a
SOONG_CONFIG_acme_feature := true SOONG_CONFIG_acme_feature := true
@ -473,12 +484,21 @@ acme_cc_defaults {
soc_b: { soc_b: {
cflags: ["-DSOC_B"], cflags: ["-DSOC_B"],
}, },
conditions_default: {
cflags: ["-DSOC_DEFAULT"],
},
}, },
feature: { feature: {
cflags: ["-DFEATURE"], cflags: ["-DFEATURE"],
conditions_default: {
cflags: ["-DFEATURE_DEFAULT"],
},
}, },
width: { width: {
cflags: ["-DWIDTH=%s"], cflags: ["-DWIDTH=%s"],
conditions_default: {
cflags: ["-DWIDTH=DEFAULT"],
},
}, },
}, },
} }
@ -490,8 +510,37 @@ cc_library {
} }
``` ```
With the `BoardConfig.mk` snippet above, libacme_foo would build with With the `BoardConfig.mk` snippet above, `libacme_foo` would build with
cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200". `cflags: "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200"`.
Alternatively, with `DefaultBoardConfig.mk`:
```
SOONG_CONFIG_NAMESPACES += acme
SOONG_CONFIG_acme += \
board \
feature \
width \
SOONG_CONFIG_acme_feature := false
```
then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
Alternatively, with `DefaultBoardConfig.mk`:
```
SOONG_CONFIG_NAMESPACES += acme
SOONG_CONFIG_acme += \
board \
feature \
width \
SOONG_CONFIG_acme_board := soc_c
```
then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT
-DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
`soong_config_module_type` modules will work best when used to wrap defaults `soong_config_module_type` modules will work best when used to wrap defaults
modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced

View file

@ -51,6 +51,16 @@ type soongConfigModuleTypeImportProperties struct {
// variables from another Android.bp file. The imported module type will exist for all // variables from another Android.bp file. The imported module type will exist for all
// modules after the import in the Android.bp file. // modules after the import in the Android.bp file.
// //
// Each soong_config_variable supports an additional value `conditions_default`. The properties
// specified in `conditions_default` will only be used under the following conditions:
// bool variable: the variable is unspecified or not set to a true value
// value variable: the variable is unspecified
// string variable: the variable is unspecified or the variable is set to a string unused in the
// given module. For example, string variable `test` takes values: "a" and "b",
// if the module contains a property `a` and `conditions_default`, when test=b,
// the properties under `conditions_default` will be used. To specify that no
// properties should be amended for `b`, you can set `b: {},`.
//
// For example, an Android.bp file could have: // For example, an Android.bp file could have:
// //
// soong_config_module_type_import { // soong_config_module_type_import {
@ -69,12 +79,21 @@ type soongConfigModuleTypeImportProperties struct {
// soc_b: { // soc_b: {
// cflags: ["-DSOC_B"], // cflags: ["-DSOC_B"],
// }, // },
// conditions_default: {
// cflags: ["-DSOC_DEFAULT"],
// },
// }, // },
// feature: { // feature: {
// cflags: ["-DFEATURE"], // cflags: ["-DFEATURE"],
// conditions_default: {
// cflags: ["-DFEATURE_DEFAULT"],
// },
// }, // },
// width: { // width: {
// cflags: ["-DWIDTH=%s"], // cflags: ["-DWIDTH=%s"],
// conditions_default: {
// cflags: ["-DWIDTH=DEFAULT"],
// },
// }, // },
// }, // },
// } // }
@ -99,7 +118,7 @@ type soongConfigModuleTypeImportProperties struct {
// //
// soong_config_string_variable { // soong_config_string_variable {
// name: "board", // name: "board",
// values: ["soc_a", "soc_b"], // values: ["soc_a", "soc_b", "soc_c"],
// } // }
// //
// If an acme BoardConfig.mk file contained: // If an acme BoardConfig.mk file contained:
@ -114,6 +133,31 @@ type soongConfigModuleTypeImportProperties struct {
// SOONG_CONFIG_acme_width := 200 // SOONG_CONFIG_acme_width := 200
// //
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200". // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
//
// Alternatively, if acme BoardConfig.mk file contained:
//
// SOONG_CONFIG_NAMESPACES += acme
// SOONG_CONFIG_acme += \
// board \
// feature \
//
// SOONG_CONFIG_acme_feature := false
//
// Then libacme_foo would build with cflags:
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
//
// Similarly, if acme BoardConfig.mk file contained:
//
// SOONG_CONFIG_NAMESPACES += acme
// SOONG_CONFIG_acme += \
// board \
// feature \
//
// SOONG_CONFIG_acme_board := soc_c
//
// Then libacme_foo would build with cflags:
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
func soongConfigModuleTypeImportFactory() Module { func soongConfigModuleTypeImportFactory() Module {
module := &soongConfigModuleTypeImport{} module := &soongConfigModuleTypeImport{}
@ -148,6 +192,16 @@ type soongConfigModuleTypeModule struct {
// in an Android.bp file, and can be imported into other Android.bp files using // in an Android.bp file, and can be imported into other Android.bp files using
// soong_config_module_type_import. // soong_config_module_type_import.
// //
// Each soong_config_variable supports an additional value `conditions_default`. The properties
// specified in `conditions_default` will only be used under the following conditions:
// bool variable: the variable is unspecified or not set to a true value
// value variable: the variable is unspecified
// string variable: the variable is unspecified or the variable is set to a string unused in the
// given module. For example, string variable `test` takes values: "a" and "b",
// if the module contains a property `a` and `conditions_default`, when test=b,
// the properties under `conditions_default` will be used. To specify that no
// properties should be amended for `b`, you can set `b: {},`.
//
// For example, an Android.bp file could have: // For example, an Android.bp file could have:
// //
// soong_config_module_type { // soong_config_module_type {
@ -176,12 +230,21 @@ type soongConfigModuleTypeModule struct {
// soc_b: { // soc_b: {
// cflags: ["-DSOC_B"], // cflags: ["-DSOC_B"],
// }, // },
// conditions_default: {
// cflags: ["-DSOC_DEFAULT"],
// },
// }, // },
// feature: { // feature: {
// cflags: ["-DFEATURE"], // cflags: ["-DFEATURE"],
// conditions_default: {
// cflags: ["-DFEATURE_DEFAULT"],
// },
// }, // },
// width: { // width: {
// cflags: ["-DWIDTH=%s"], // cflags: ["-DWIDTH=%s"],
// conditions_default: {
// cflags: ["-DWIDTH=DEFAULT"],
// },
// }, // },
// }, // },
// } // }

View file

@ -60,15 +60,20 @@ func TestSoongConfigModule(t *testing.T) {
name: "acme_test", name: "acme_test",
module_type: "test", module_type: "test",
config_namespace: "acme", config_namespace: "acme",
variables: ["board", "feature1", "FEATURE3"], variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
bool_variables: ["feature2"], bool_variables: ["feature2", "unused_feature"],
value_variables: ["size"], value_variables: ["size", "unused_size"],
properties: ["cflags", "srcs", "defaults"], properties: ["cflags", "srcs", "defaults"],
} }
soong_config_string_variable { soong_config_string_variable {
name: "board", name: "board",
values: ["soc_a", "soc_b"], values: ["soc_a", "soc_b", "soc_c", "soc_d"],
}
soong_config_string_variable {
name: "unused_string_var",
values: ["a", "b"],
} }
soong_config_bool_variable { soong_config_bool_variable {
@ -105,15 +110,28 @@ func TestSoongConfigModule(t *testing.T) {
soc_b: { soc_b: {
cflags: ["-DSOC_B"], cflags: ["-DSOC_B"],
}, },
soc_c: {},
conditions_default: {
cflags: ["-DSOC_CONDITIONS_DEFAULT"],
},
}, },
size: { size: {
cflags: ["-DSIZE=%s"], cflags: ["-DSIZE=%s"],
conditions_default: {
cflags: ["-DSIZE=CONDITIONS_DEFAULT"],
},
}, },
feature1: { feature1: {
conditions_default: {
cflags: ["-DF1_CONDITIONS_DEFAULT"],
},
cflags: ["-DFEATURE1"], cflags: ["-DFEATURE1"],
}, },
feature2: { feature2: {
cflags: ["-DFEATURE2"], cflags: ["-DFEATURE2"],
conditions_default: {
cflags: ["-DF2_CONDITIONS_DEFAULT"],
},
}, },
FEATURE3: { FEATURE3: {
cflags: ["-DFEATURE3"], cflags: ["-DFEATURE3"],
@ -145,6 +163,7 @@ func TestSoongConfigModule(t *testing.T) {
cflags: ["-DSOC_B"], cflags: ["-DSOC_B"],
defaults: ["foo_defaults_b"], defaults: ["foo_defaults_b"],
}, },
soc_c: {},
}, },
size: { size: {
cflags: ["-DSIZE=%s"], cflags: ["-DSIZE=%s"],
@ -163,43 +182,120 @@ func TestSoongConfigModule(t *testing.T) {
` `
run := func(t *testing.T, bp string, fs map[string][]byte) { run := func(t *testing.T, bp string, fs map[string][]byte) {
config := TestConfig(buildDir, nil, bp, fs) testCases := []struct {
name string
config Config
fooExpectedFlags []string
fooDefaultsExpectedFlags []string
}{
{
name: "withValues",
config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
"acme": map[string]string{
"board": "soc_a",
"size": "42",
"feature1": "true",
"feature2": "false",
// FEATURE3 unset
"unused_feature": "true", // unused
"unused_size": "1", // unused
"unused_string_var": "a", // unused
},
}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=42",
"-DSOC_A",
"-DFEATURE1",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT_A",
"DEFAULT",
"-DGENERIC",
"-DSIZE=42",
"-DSOC_A",
"-DFEATURE1",
},
},
{
name: "empty_prop_for_string_var",
config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
"acme": map[string]string{"board": "soc_c"}}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
},
},
{
name: "unused_string_var",
config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
"acme": map[string]string{"board": "soc_d"}}),
fooExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
"-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DSOC_CONDITIONS_DEFAULT", // foo does not contain a prop "soc_d", so we use the default
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
},
},
config.TestProductVariables.VendorVars = map[string]map[string]string{ {
"acme": map[string]string{ name: "conditions_default",
"board": "soc_a", config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{}),
"size": "42", fooExpectedFlags: []string{
"feature1": "true", "DEFAULT",
"feature2": "false", "-DGENERIC",
// FEATURE3 unset "-DF2_CONDITIONS_DEFAULT",
"-DSIZE=CONDITIONS_DEFAULT",
"-DSOC_CONDITIONS_DEFAULT",
"-DF1_CONDITIONS_DEFAULT",
},
fooDefaultsExpectedFlags: []string{
"DEFAULT",
"-DGENERIC",
},
}, },
} }
ctx := NewTestContext(config) for _, tc := range testCases {
ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory) ctx := NewTestContext(tc.config)
ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory) ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory) ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory) ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory) ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
ctx.RegisterModuleType("test", soongConfigTestModuleFactory) ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
ctx.PreArchMutators(RegisterDefaultsPreArchMutators) ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
ctx.Register() ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
ctx.Register()
_, errs := ctx.ParseBlueprintsFiles("Android.bp") _, errs := ctx.ParseBlueprintsFiles("Android.bp")
FailIfErrored(t, errs) FailIfErrored(t, errs)
_, errs = ctx.PrepareBuildActions(config) _, errs = ctx.PrepareBuildActions(tc.config)
FailIfErrored(t, errs) FailIfErrored(t, errs)
basicCFlags := []string{"DEFAULT", "-DGENERIC", "-DSIZE=42", "-DSOC_A", "-DFEATURE1"} foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
if g, w := foo.props.Cflags, tc.fooExpectedFlags; !reflect.DeepEqual(g, w) {
t.Errorf("%s: wanted foo cflags %q, got %q", tc.name, w, g)
}
foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule) fooDefaults := ctx.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
if g, w := foo.props.Cflags, basicCFlags; !reflect.DeepEqual(g, w) { if g, w := fooDefaults.props.Cflags, tc.fooDefaultsExpectedFlags; !reflect.DeepEqual(g, w) {
t.Errorf("wanted foo cflags %q, got %q", w, g) t.Errorf("%s: wanted foo_with_defaults cflags %q, got %q", tc.name, w, g)
} }
fooDefaults := ctx.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
if g, w := fooDefaults.props.Cflags, append([]string{"DEFAULT_A"}, basicCFlags...); !reflect.DeepEqual(g, w) {
t.Errorf("wanted foo_with_defaults cflags %q, got %q", w, g)
} }
} }
@ -214,3 +310,11 @@ func TestSoongConfigModule(t *testing.T) {
}) })
}) })
} }
func testConfigWithVendorVars(buildDir, bp string, fs map[string][]byte, vendorVars map[string]map[string]string) Config {
config := TestConfig(buildDir, nil, bp, fs)
config.TestProductVariables.VendorVars = vendorVars
return config
}

View file

@ -10,4 +10,7 @@ bootstrap_go_package {
"config.go", "config.go",
"modules.go", "modules.go",
], ],
testSrcs: [
"modules_test.go",
],
} }

View file

@ -14,7 +14,10 @@
package soongconfig package soongconfig
import "strings" import (
"fmt"
"strings"
)
type SoongConfig interface { type SoongConfig interface {
// Bool interprets the variable named `name` as a boolean, returning true if, after // Bool interprets the variable named `name` as a boolean, returning true if, after
@ -31,7 +34,16 @@ type SoongConfig interface {
} }
func Config(vars map[string]string) SoongConfig { func Config(vars map[string]string) SoongConfig {
return soongConfig(vars) configVars := make(map[string]string)
if len(vars) > 0 {
for k, v := range vars {
configVars[k] = v
}
if _, exists := configVars[conditionsDefault]; exists {
panic(fmt.Sprintf("%q is a reserved soong config variable name", conditionsDefault))
}
}
return soongConfig(configVars)
} }
type soongConfig map[string]string type soongConfig map[string]string

View file

@ -26,6 +26,8 @@ import (
"github.com/google/blueprint/proptools" "github.com/google/blueprint/proptools"
) )
const conditionsDefault = "conditions_default"
var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables") var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the // loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
@ -145,32 +147,10 @@ func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []
return errs return errs
} }
mt := &ModuleType{ if mt, errs := newModuleType(props); len(errs) > 0 {
affectableProperties: props.Properties, return errs
ConfigNamespace: props.Config_namespace, } else {
BaseModuleType: props.Module_type, v.ModuleTypes[props.Name] = mt
variableNames: props.Variables,
}
v.ModuleTypes[props.Name] = mt
for _, name := range props.Bool_variables {
if name == "" {
return []error{fmt.Errorf("bool_variable name must not be blank")}
}
mt.Variables = append(mt.Variables, newBoolVariable(name))
}
for _, name := range props.Value_variables {
if name == "" {
return []error{fmt.Errorf("value_variables entry must not be blank")}
}
mt.Variables = append(mt.Variables, &valueVariable{
baseVariable: baseVariable{
variable: name,
},
})
} }
return nil return nil
@ -196,6 +176,12 @@ func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (err
return []error{fmt.Errorf("values property must be set")} return []error{fmt.Errorf("values property must be set")}
} }
for _, name := range stringProps.Values {
if err := checkVariableName(name); err != nil {
return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
}
}
v.variables[base.variable] = &stringVariable{ v.variables[base.variable] = &stringVariable{
baseVariable: base, baseVariable: base,
values: CanonicalizeToProperties(stringProps.Values), values: CanonicalizeToProperties(stringProps.Values),
@ -417,8 +403,7 @@ func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.
// PropertiesToApply returns the applicable properties from a ModuleType that should be applied // PropertiesToApply returns the applicable properties from a ModuleType that should be applied
// based on SoongConfig values. // based on SoongConfig values.
// Expects that props contains a struct field with name soong_config_variables. The fields within // Expects that props contains a struct field with name soong_config_variables. The fields within
// soong_config_variables are expected to be in the same order as moduleType.Variables. In general, // soong_config_variables are expected to be in the same order as moduleType.Variables.
// props should be generated via CreateProperties.
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) { func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
var ret []interface{} var ret []interface{}
props = props.Elem().FieldByName(soongConfigProperty) props = props.Elem().FieldByName(soongConfigProperty)
@ -441,6 +426,46 @@ type ModuleType struct {
variableNames []string variableNames []string
} }
func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
mt := &ModuleType{
affectableProperties: props.Properties,
ConfigNamespace: props.Config_namespace,
BaseModuleType: props.Module_type,
variableNames: props.Variables,
}
for _, name := range props.Bool_variables {
if err := checkVariableName(name); err != nil {
return nil, []error{fmt.Errorf("bool_variables %s", err)}
}
mt.Variables = append(mt.Variables, newBoolVariable(name))
}
for _, name := range props.Value_variables {
if err := checkVariableName(name); err != nil {
return nil, []error{fmt.Errorf("value_variables %s", err)}
}
mt.Variables = append(mt.Variables, &valueVariable{
baseVariable: baseVariable{
variable: name,
},
})
}
return mt, nil
}
func checkVariableName(name string) error {
if name == "" {
return fmt.Errorf("name must not be blank")
} else if name == conditionsDefault {
return fmt.Errorf("%q is reserved", conditionsDefault)
}
return nil
}
type soongConfigVariable interface { type soongConfigVariable interface {
// variableProperty returns the name of the variable. // variableProperty returns the name of the variable.
variableProperty() string variableProperty() string
@ -474,7 +499,10 @@ type stringVariable struct {
func (s *stringVariable) variableValuesType() reflect.Type { func (s *stringVariable) variableValuesType() reflect.Type {
var fields []reflect.StructField var fields []reflect.StructField
for _, v := range s.values { var values []string
values = append(values, s.values...)
values = append(values, conditionsDefault)
for _, v := range values {
fields = append(fields, reflect.StructField{ fields = append(fields, reflect.StructField{
Name: proptools.FieldNameForProperty(v), Name: proptools.FieldNameForProperty(v),
Type: emptyInterfaceType, Type: emptyInterfaceType,
@ -484,26 +512,36 @@ func (s *stringVariable) variableValuesType() reflect.Type {
return reflect.StructOf(fields) return reflect.StructOf(fields)
} }
// initializeProperties initializes properties to zero value of typ for supported values and a final
// conditions default field.
func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) { func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
for i := range s.values { for i := range s.values {
v.Field(i).Set(reflect.Zero(typ)) v.Field(i).Set(reflect.Zero(typ))
} }
v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
} }
// Extracts an interface from values containing the properties to apply based on config.
// If config does not match a value with a non-nil property set, the default value will be returned.
func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) { func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
for j, v := range s.values { for j, v := range s.values {
if config.String(s.variable) == v { f := values.Field(j)
return values.Field(j).Interface(), nil if config.String(s.variable) == v && !f.Elem().IsNil() {
return f.Interface(), nil
} }
} }
// if we have reached this point, we have checked all valid values of string and either:
return nil, nil // * the value was not set
// * the value was set but that value was not specified in the Android.bp file
return values.Field(len(s.values)).Interface(), nil
} }
// Struct to allow conditions set based on a boolean variable
type boolVariable struct { type boolVariable struct {
baseVariable baseVariable
} }
// newBoolVariable constructs a boolVariable with the given name
func newBoolVariable(name string) *boolVariable { func newBoolVariable(name string) *boolVariable {
return &boolVariable{ return &boolVariable{
baseVariable{ baseVariable{
@ -516,18 +554,82 @@ func (b boolVariable) variableValuesType() reflect.Type {
return emptyInterfaceType return emptyInterfaceType
} }
// initializeProperties initializes a property to zero value of typ with an additional conditions
// default field.
func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) { func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
v.Set(reflect.Zero(typ)) initializePropertiesWithDefault(v, typ)
} }
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) { // initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
if config.Bool(b.variable) { // in typ, with an additional field for defaults of type typ. This should be used to initialize
return values.Interface(), nil // boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
// one variable and a default.
func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
sTyp := typ.Elem()
var fields []reflect.StructField
for i := 0; i < sTyp.NumField(); i++ {
fields = append(fields, sTyp.Field(i))
} }
// create conditions_default field
nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
fields = append(fields, reflect.StructField{
Name: nestedFieldName,
Type: typ,
})
newTyp := reflect.PtrTo(reflect.StructOf(fields))
v.Set(reflect.Zero(newTyp))
}
// conditionsDefaultField extracts the conditions_default field from v. This is always the final
// field if initialized with initializePropertiesWithDefault.
func conditionsDefaultField(v reflect.Value) reflect.Value {
return v.Field(v.NumField() - 1)
}
// removeDefault removes the conditions_default field from values while retaining values from all
// other fields. This allows
func removeDefault(values reflect.Value) reflect.Value {
v := values.Elem().Elem()
s := conditionsDefaultField(v)
// if conditions_default field was not set, there will be no issues extending properties.
if !s.IsValid() {
return v
}
// If conditions_default field was set, it has the correct type for our property. Create a new
// reflect.Value of the conditions_default type and copy all fields (except for
// conditions_default) based on values to the result.
res := reflect.New(s.Type().Elem())
for i := 0; i < res.Type().Elem().NumField(); i++ {
val := v.Field(i)
res.Elem().Field(i).Set(val)
}
return res
}
// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
// the module. If the value was not set, conditions_default interface will be returned; otherwise,
// the interface in values, without conditions_default will be returned.
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
// If this variable was not referenced in the module, there are no properties to apply.
if values.Elem().IsZero() {
return nil, nil
}
if config.Bool(b.variable) {
values = removeDefault(values)
return values.Interface(), nil
}
v := values.Elem().Elem()
if f := conditionsDefaultField(v); f.IsValid() {
return f.Interface(), nil
}
return nil, nil return nil, nil
} }
// Struct to allow conditions set based on a value variable, supporting string substitution.
type valueVariable struct { type valueVariable struct {
baseVariable baseVariable
} }
@ -536,17 +638,28 @@ func (s *valueVariable) variableValuesType() reflect.Type {
return emptyInterfaceType return emptyInterfaceType
} }
// initializeProperties initializes a property to zero value of typ with an additional conditions
// default field.
func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) { func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
v.Set(reflect.Zero(typ)) initializePropertiesWithDefault(v, typ)
} }
// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
// the module. If the variable was not set, conditions_default interface will be returned;
// otherwise, the interface in values, without conditions_default will be returned with all
// appropriate string substitutions based on variable being set.
func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) { func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
if !config.IsSet(s.variable) || !values.IsValid() { // If this variable was not referenced in the module, there are no properties to apply.
if !values.IsValid() || values.Elem().IsZero() {
return nil, nil return nil, nil
} }
if !config.IsSet(s.variable) {
return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
}
configValue := config.String(s.variable) configValue := config.String(s.variable)
propStruct := values.Elem().Elem() values = removeDefault(values)
propStruct := values.Elem()
if !propStruct.IsValid() { if !propStruct.IsValid() {
return nil, nil return nil, nil
} }

View file

@ -254,67 +254,75 @@ type properties struct {
A *string A *string
B bool B bool
} }
type soongConfigVariables struct {
Bool_var properties type boolVarProps struct {
Other_bool_var properties A *string
B bool
Conditions_default *properties
} }
type soongConfigProps struct { type soongConfigVars struct {
Soong_config_variables soongConfigVariables Bool_var interface{}
} }
func Test_PropertiesToApply(t *testing.T) { func Test_PropertiesToApply(t *testing.T) {
mt, _ := newModuleType(&ModuleTypeProperties{
mt := &ModuleType{ Module_type: "foo",
BaseModuleType: "foo", Config_namespace: "bar",
ConfigNamespace: "bar", Bool_variables: []string{"bool_var"},
Variables: []soongConfigVariable{ Properties: []string{"a", "b"},
newBoolVariable("bool_var"), })
newBoolVariable("other_bool_var"), boolVarPositive := &properties{
}, A: proptools.StringPtr("A"),
affectableProperties: []string{ B: true,
"a",
"b",
},
} }
props := soongConfigProps{ conditionsDefault := &properties{
Soong_config_variables: soongConfigVariables{ A: proptools.StringPtr("default"),
Bool_var: properties{ B: false,
A: proptools.StringPtr("a"), }
B: true, actualProps := &struct {
}, Soong_config_variables soongConfigVars
Other_bool_var: properties{ }{
A: proptools.StringPtr("other"), Soong_config_variables: soongConfigVars{
B: false, Bool_var: &boolVarProps{
A: boolVarPositive.A,
B: boolVarPositive.B,
Conditions_default: conditionsDefault,
}, },
}, },
} }
props := reflect.ValueOf(actualProps)
testCases := []struct { testCases := []struct {
name string
config SoongConfig config SoongConfig
wantProps []interface{} wantProps []interface{}
}{ }{
{ {
config: Config(map[string]string{}), name: "no_vendor_config",
config: Config(map[string]string{}),
wantProps: []interface{}{conditionsDefault},
}, },
{ {
name: "vendor_config_false",
config: Config(map[string]string{"bool_var": "n"}),
wantProps: []interface{}{conditionsDefault},
},
{
name: "bool_var_true",
config: Config(map[string]string{"bool_var": "y"}), config: Config(map[string]string{"bool_var": "y"}),
wantProps: []interface{}{props.Soong_config_variables.Bool_var}, wantProps: []interface{}{boolVarPositive},
},
{
config: Config(map[string]string{"other_bool_var": "y"}),
wantProps: []interface{}{props.Soong_config_variables.Other_bool_var},
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
gotProps, err := PropertiesToApply(mt, reflect.ValueOf(&props), tc.config) gotProps, err := PropertiesToApply(mt, props, tc.config)
if err != nil { if err != nil {
t.Errorf("Unexpected error in PropertiesToApply: %s", err) t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
} }
if !reflect.DeepEqual(gotProps, tc.wantProps) { if !reflect.DeepEqual(gotProps, tc.wantProps) {
t.Errorf("Expected %s, got %s", tc.wantProps, gotProps) t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
} }
} }
} }