soong config: add value_variable substitution

There are some cases that aren't handled with the existing variable
types for booleans or known lists of strings. Similarly to our
product_variables that uses %s / %d for things like
PLATFORM_SDK_VERSION, allow vendors to define their own config variables
to be substituted into properties.

For example, some of the makefiles that I've attempted to convert had
the option to pass in version numbers from the board, or the default
display size:

  -DDISPLAY_VERSION=550
  -DDISP_H=1080

These examples happen to be integers, but since our configuration
language (make) doesn't support numbers, %s works just as well.

This change will allow the above to be represented using:

  soong_config_module_type {
      name: "acme_cc_defaults",
      module_type: "cc_defaults",
      config_namespace: "acme",
      value_variables: [
          "DISPLAY_VERSION",
	  "DISP_H",
      ],
      properties: ["cflags"],
  }

  acme_cc_defaults {
      name: "my_defaults",
      soong_config_variables: {
          DISPLAY_VERSION: {
	      cflags: ["-DDISPLAY_VERSION=%s"],
	  },
	  DISP_H: {
	      cflags: ["-DDISP_H=%s"],
	  }
      },
  }

Test: built-in tests
Change-Id: I18f35746b5cc39c304a136980249e886d38c6df6
This commit is contained in:
Dan Willemsen 2020-03-23 19:42:18 -07:00
parent 2b8b89cfa2
commit b0935db8c3
4 changed files with 131 additions and 15 deletions

View File

@ -421,6 +421,7 @@ soong_config_module_type {
config_namespace: "acme",
variables: ["board"],
bool_variables: ["feature"],
value_variables: ["width"],
properties: ["cflags", "srcs"],
}
@ -431,8 +432,9 @@ soong_config_string_variable {
```
This example describes a new `acme_cc_defaults` module type that extends the
`cc_defaults` module type, with two additional conditionals based on variables
`board` and `feature`, which can affect properties `cflags` and `srcs`.
`cc_defaults` module type, with three additional conditionals based on
variables `board`, `feature` and `width`, which can affect properties `cflags`
and `srcs`.
The values of the variables can be set from a product's `BoardConfig.mk` file:
```
@ -443,6 +445,7 @@ SOONG_CONFIG_acme += \
SOONG_CONFIG_acme_board := soc_a
SOONG_CONFIG_acme_feature := true
SOONG_CONFIG_acme_width := 200
```
The `acme_cc_defaults` module type can be used anywhere after the definition in
@ -471,6 +474,9 @@ acme_cc_defaults {
feature: {
cflags: ["-DFEATURE"],
},
width: {
cflags: ["-DWIDTH=%s"],
},
},
}
@ -482,7 +488,7 @@ cc_library {
```
With the `BoardConfig.mk` snippet above, libacme_foo would build with
cflags "-DGENERIC -DSOC_A -DFEATURE".
cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
`soong_config_module_type` modules will work best when used to wrap defaults
modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced

View File

@ -73,6 +73,9 @@ type soongConfigModuleTypeImportProperties struct {
// feature: {
// cflags: ["-DFEATURE"],
// },
// width: {
// cflags: ["-DWIDTH=%s"],
// },
// },
// }
//
@ -90,6 +93,7 @@ type soongConfigModuleTypeImportProperties struct {
// config_namespace: "acme",
// variables: ["board"],
// bool_variables: ["feature"],
// value_variables: ["width"],
// properties: ["cflags", "srcs"],
// }
//
@ -107,8 +111,9 @@ type soongConfigModuleTypeImportProperties struct {
//
// SOONG_CONFIG_acme_board := soc_a
// SOONG_CONFIG_acme_feature := true
// SOONG_CONFIG_acme_width := 200
//
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
func soongConfigModuleTypeImportFactory() Module {
module := &soongConfigModuleTypeImport{}
@ -148,6 +153,7 @@ type soongConfigModuleTypeModule struct {
// config_namespace: "acme",
// variables: ["board"],
// bool_variables: ["feature"],
// value_variables: ["width"],
// properties: ["cflags", "srcs"],
// }
//
@ -171,6 +177,9 @@ type soongConfigModuleTypeModule struct {
// feature: {
// cflags: ["-DFEATURE"],
// },
// width: {
// cflags: ["-DWIDTH=%s"],
// },
// },
// }
//
@ -189,6 +198,7 @@ type soongConfigModuleTypeModule struct {
//
// SOONG_CONFIG_acme_board := soc_a
// SOONG_CONFIG_acme_feature := true
// SOONG_CONFIG_acme_width := 200
//
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
func soongConfigModuleTypeFactory() Module {
@ -349,7 +359,12 @@ func soongConfigModuleFactory(factory blueprint.ModuleFactory,
AddLoadHook(module, func(ctx LoadHookContext) {
config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
if err != nil {
ctx.ModuleErrorf("%s", err)
return
}
for _, ps := range newProps {
ctx.AppendProperties(ps)
}
})

View File

@ -45,6 +45,7 @@ func TestSoongConfigModule(t *testing.T) {
config_namespace: "acme",
variables: ["board", "feature1", "FEATURE3"],
bool_variables: ["feature2"],
value_variables: ["size"],
properties: ["cflags", "srcs"],
}
@ -82,6 +83,9 @@ func TestSoongConfigModule(t *testing.T) {
cflags: ["-DSOC_B"],
},
},
size: {
cflags: ["-DSIZE=%s"],
},
feature1: {
cflags: ["-DFEATURE1"],
},
@ -101,6 +105,7 @@ func TestSoongConfigModule(t *testing.T) {
config.TestProductVariables.VendorVars = map[string]map[string]string{
"acme": map[string]string{
"board": "soc_a",
"size": "42",
"feature1": "true",
"feature2": "false",
// FEATURE3 unset
@ -121,7 +126,7 @@ func TestSoongConfigModule(t *testing.T) {
FailIfErrored(t, errs)
foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSIZE=42", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
t.Errorf("wanted foo cflags %q, got %q", w, g)
}
}

View File

@ -112,6 +112,10 @@ type ModuleTypeProperties struct {
// the list of boolean SOONG_CONFIG variables that this module type will read
Bool_variables []string
// the list of SOONG_CONFIG variables that this module type will read. The value will be
// inserted into the properties with %s substitution.
Value_variables []string
// the list of properties that this module type will extend.
Properties []string
}
@ -161,6 +165,18 @@ func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []
})
}
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
}
@ -404,15 +420,17 @@ func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.
// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
// based on SoongConfig values.
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) []interface{} {
func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
var ret []interface{}
props = props.Elem().FieldByName(soongConfigProperty)
for i, c := range moduleType.Variables {
if ps := c.PropertiesToApply(config, props.Field(i)); ps != nil {
if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
return nil, err
} else if ps != nil {
ret = append(ret, ps)
}
}
return ret
return ret, nil
}
type ModuleType struct {
@ -438,7 +456,7 @@ type soongConfigVariable interface {
// PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
// to the module.
PropertiesToApply(config SoongConfig, values reflect.Value) interface{}
PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
}
type baseVariable struct {
@ -473,14 +491,14 @@ func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type)
}
}
func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
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()
return values.Field(j).Interface(), nil
}
}
return nil
return nil, nil
}
type boolVariable struct {
@ -495,11 +513,83 @@ func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
v.Set(reflect.Zero(typ))
}
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
if config.Bool(b.variable) {
return values.Interface()
return values.Interface(), nil
}
return nil, nil
}
type valueVariable struct {
baseVariable
}
func (s *valueVariable) variableValuesType() reflect.Type {
return emptyInterfaceType
}
func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
v.Set(reflect.Zero(typ))
}
func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
if !config.IsSet(s.variable) {
return nil, nil
}
configValue := config.String(s.variable)
propStruct := values.Elem().Elem()
for i := 0; i < propStruct.NumField(); i++ {
field := propStruct.Field(i)
kind := field.Kind()
if kind == reflect.Ptr {
if field.IsNil() {
continue
}
field = field.Elem()
}
switch kind {
case reflect.String:
err := printfIntoProperty(field, configValue)
if err != nil {
return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
}
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
err := printfIntoProperty(field.Index(j), configValue)
if err != nil {
return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
}
}
case reflect.Bool:
// Nothing to do
default:
return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
}
}
return values.Interface(), nil
}
func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
s := propertyValue.String()
count := strings.Count(s, "%")
if count == 0 {
return nil
}
if count > 1 {
return fmt.Errorf("value variable properties only support a single '%%'")
}
if !strings.Contains(s, "%s") {
return fmt.Errorf("unsupported %% in value variable property")
}
propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
return nil
}