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

@@ -26,6 +26,8 @@ import (
"github.com/google/blueprint/proptools"
)
const conditionsDefault = "conditions_default"
var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
// 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
}
mt := &ModuleType{
affectableProperties: props.Properties,
ConfigNamespace: props.Config_namespace,
BaseModuleType: props.Module_type,
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,
},
})
if mt, errs := newModuleType(props); len(errs) > 0 {
return errs
} else {
v.ModuleTypes[props.Name] = mt
}
return nil
@@ -196,6 +176,12 @@ func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (err
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{
baseVariable: base,
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
// based on SoongConfig values.
// 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,
// props should be generated via CreateProperties.
// soong_config_variables are expected to be in the same order as moduleType.Variables.
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
var ret []interface{}
props = props.Elem().FieldByName(soongConfigProperty)
@@ -441,6 +426,46 @@ type ModuleType struct {
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 {
// variableProperty returns the name of the variable.
variableProperty() string
@@ -474,7 +499,10 @@ type stringVariable struct {
func (s *stringVariable) variableValuesType() reflect.Type {
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{
Name: proptools.FieldNameForProperty(v),
Type: emptyInterfaceType,
@@ -484,26 +512,36 @@ func (s *stringVariable) variableValuesType() reflect.Type {
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) {
for i := range s.values {
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) {
for j, v := range s.values {
if config.String(s.variable) == v {
return values.Field(j).Interface(), nil
f := values.Field(j)
if config.String(s.variable) == v && !f.Elem().IsNil() {
return f.Interface(), nil
}
}
return nil, nil
// if we have reached this point, we have checked all valid values of string and either:
// * 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 {
baseVariable
}
// newBoolVariable constructs a boolVariable with the given name
func newBoolVariable(name string) *boolVariable {
return &boolVariable{
baseVariable{
@@ -516,18 +554,82 @@ func (b boolVariable) variableValuesType() reflect.Type {
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) {
v.Set(reflect.Zero(typ))
initializePropertiesWithDefault(v, typ)
}
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
if config.Bool(b.variable) {
return values.Interface(), nil
// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
// in typ, with an additional field for defaults of type typ. This should be used to initialize
// 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
}
// Struct to allow conditions set based on a value variable, supporting string substitution.
type valueVariable struct {
baseVariable
}
@@ -536,17 +638,28 @@ func (s *valueVariable) variableValuesType() reflect.Type {
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) {
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) {
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
}
if !config.IsSet(s.variable) {
return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
}
configValue := config.String(s.variable)
propStruct := values.Elem().Elem()
values = removeDefault(values)
propStruct := values.Elem()
if !propStruct.IsValid() {
return nil, nil
}