Merge "Add support to Soong config list variable" into main

This commit is contained in:
Treehugger Robot 2024-04-23 01:14:44 +00:00 committed by Gerrit Code Review
commit d47220d470
4 changed files with 266 additions and 58 deletions

View file

@ -449,6 +449,7 @@ soong_config_module_type {
config_namespace: "acme",
variables: ["board"],
bool_variables: ["feature"],
list_variables: ["impl"],
value_variables: ["width"],
properties: ["cflags", "srcs"],
}
@ -460,12 +461,13 @@ soong_config_string_variable {
```
This example describes a new `acme_cc_defaults` module type that extends the
`cc_defaults` module type, with three additional conditionals based on
variables `board`, `feature` and `width`, which can affect properties `cflags`
and `srcs`. Additionally, each conditional will contain a `conditions_default`
`cc_defaults` module type, with four additional conditionals based on variables
`board`, `feature`, `impl` and `width` which can affect properties `cflags` 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
* list variable (e.g. `impl`): the variable is unspecified
* 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`
@ -478,6 +480,7 @@ The values of the variables can be set from a product's `BoardConfig.mk` file:
```
$(call soong_config_set,acme,board,soc_a)
$(call soong_config_set,acme,feature,true)
$(call soong_config_set,acme,impl,foo.cpp bar.cpp)
$(call soong_config_set,acme,width,200)
```
@ -519,6 +522,12 @@ acme_cc_defaults {
cflags: ["-DWIDTH=DEFAULT"],
},
},
impl: {
srcs: ["impl/%s"],
conditions_default: {
srcs: ["impl/default.cpp"],
},
},
},
}
@ -530,7 +539,8 @@ cc_library {
```
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"` and
`srcs: ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"]`.
Alternatively, with `DefaultBoardConfig.mk`:
@ -539,12 +549,14 @@ SOONG_CONFIG_NAMESPACES += acme
SOONG_CONFIG_acme += \
board \
feature \
impl \
width \
SOONG_CONFIG_acme_feature := false
```
then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"`
and `srcs: ["*.cpp", "impl/default.cpp"]`.
Alternatively, with `DefaultBoardConfig.mk`:
@ -553,13 +565,15 @@ SOONG_CONFIG_NAMESPACES += acme
SOONG_CONFIG_acme += \
board \
feature \
impl \
width \
SOONG_CONFIG_acme_board := soc_c
SOONG_CONFIG_acme_impl := baz
```
then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT
-DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
-DFEATURE_DEFAULT -DSIZE=DEFAULT"` and `srcs: ["*.cpp", "impl/baz.cpp"]`.
`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

@ -64,6 +64,7 @@ type soongConfigModuleTypeImportProperties struct {
// 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
// list 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,
@ -104,6 +105,12 @@ type soongConfigModuleTypeImportProperties struct {
// cflags: ["-DWIDTH=DEFAULT"],
// },
// },
// impl: {
// srcs: ["impl/%s"],
// conditions_default: {
// srcs: ["impl/default.cpp"],
// },
// },
// },
// }
//
@ -122,6 +129,7 @@ type soongConfigModuleTypeImportProperties struct {
// variables: ["board"],
// bool_variables: ["feature"],
// value_variables: ["width"],
// list_variables: ["impl"],
// properties: ["cflags", "srcs"],
// }
//
@ -135,8 +143,10 @@ type soongConfigModuleTypeImportProperties struct {
// $(call add_soong_config_var_value, acme, board, soc_a)
// $(call add_soong_config_var_value, acme, feature, true)
// $(call add_soong_config_var_value, acme, width, 200)
// $(call add_soong_config_var_value, acme, impl, foo.cpp bar.cpp)
//
// 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" and srcs
// ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
//
// Alternatively, if acme BoardConfig.mk file contained:
//
@ -148,7 +158,9 @@ type soongConfigModuleTypeImportProperties struct {
// SOONG_CONFIG_acme_feature := false
//
// Then libacme_foo would build with cflags:
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"
// and with srcs:
// ["*.cpp", "impl/default.cpp"].
//
// Similarly, if acme BoardConfig.mk file contained:
//
@ -158,9 +170,13 @@ type soongConfigModuleTypeImportProperties struct {
// feature \
//
// SOONG_CONFIG_acme_board := soc_c
// SOONG_CONFIG_acme_impl := foo.cpp bar.cpp
//
// Then libacme_foo would build with cflags:
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"
// and with srcs:
// ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
//
func SoongConfigModuleTypeImportFactory() Module {
module := &soongConfigModuleTypeImport{}
@ -201,6 +217,7 @@ type soongConfigModuleTypeModule struct {
//
// bool variable: the variable is unspecified or not set to a true value
// value variable: the variable is unspecified
// list 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,
@ -209,56 +226,63 @@ type soongConfigModuleTypeModule struct {
//
// For example, an Android.bp file could have:
//
// 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"],
// }
// soong_config_module_type {
// name: "acme_cc_defaults",
// module_type: "cc_defaults",
// config_namespace: "acme",
// variables: ["board"],
// bool_variables: ["feature"],
// value_variables: ["width"],
// list_variables: ["impl"],
// properties: ["cflags", "srcs"],
// }
//
// soong_config_string_variable {
// name: "board",
// values: ["soc_a", "soc_b"],
// }
// soong_config_string_variable {
// name: "board",
// values: ["soc_a", "soc_b"],
// }
//
// acme_cc_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"],
// },
// acme_cc_defaults {
// name: "acme_defaults",
// cflags: ["-DGENERIC"],
// soong_config_variables: {
// board: {
// soc_a: {
// cflags: ["-DSOC_A"],
// },
// feature: {
// cflags: ["-DFEATURE"],
// conditions_default: {
// cflags: ["-DFEATURE_DEFAULT"],
// },
// soc_b: {
// cflags: ["-DSOC_B"],
// },
// width: {
// cflags: ["-DWIDTH=%s"],
// conditions_default: {
// cflags: ["-DWIDTH=DEFAULT"],
// },
// conditions_default: {
// cflags: ["-DSOC_DEFAULT"],
// },
// },
// }
// feature: {
// cflags: ["-DFEATURE"],
// conditions_default: {
// cflags: ["-DFEATURE_DEFAULT"],
// },
// },
// width: {
// cflags: ["-DWIDTH=%s"],
// conditions_default: {
// cflags: ["-DWIDTH=DEFAULT"],
// },
// },
// impl: {
// srcs: ["impl/%s"],
// conditions_default: {
// srcs: ["impl/default.cpp"],
// },
// },
// },
// }
//
// cc_library {
// name: "libacme_foo",
// defaults: ["acme_defaults"],
// srcs: ["*.cpp"],
// }
// cc_library {
// name: "libacme_foo",
// defaults: ["acme_defaults"],
// srcs: ["*.cpp"],
// }
//
// If an acme BoardConfig.mk file contained:
//
@ -270,8 +294,10 @@ type soongConfigModuleTypeModule struct {
// SOONG_CONFIG_acme_board := soc_a
// SOONG_CONFIG_acme_feature := true
// SOONG_CONFIG_acme_width := 200
// SOONG_CONFIG_acme_impl := foo.cpp bar.cpp
//
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE" and srcs
// ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
func SoongConfigModuleTypeFactory() Module {
module := &soongConfigModuleTypeModule{}

View file

@ -117,6 +117,10 @@ type ModuleTypeProperties struct {
// inserted into the properties with %s substitution.
Value_variables []string
// the list of SOONG_CONFIG list variables that this module type will read. Each value will be
// inserted into the properties with %s substitution.
List_variables []string
// the list of properties that this module type will extend.
Properties []string
}
@ -468,6 +472,18 @@ func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
})
}
for _, name := range props.List_variables {
if err := checkVariableName(name); err != nil {
return nil, []error{fmt.Errorf("list_variables %s", err)}
}
mt.Variables = append(mt.Variables, &listVariable{
baseVariable: baseVariable{
variable: name,
},
})
}
return mt, nil
}
@ -730,6 +746,90 @@ func (s *valueVariable) printfIntoPropertyRecursive(fieldName []string, propStru
return nil
}
// Struct to allow conditions set based on a list variable, supporting string substitution.
type listVariable struct {
baseVariable
}
func (s *listVariable) variableValuesType() reflect.Type {
return emptyInterfaceType
}
// initializeProperties initializes a property to zero value of typ with an additional conditions
// default field.
func (s *listVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
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 *listVariable) 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.IsValid() || values.Elem().IsZero() {
return nil, nil
}
if !config.IsSet(s.variable) {
return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
}
configValues := strings.Split(config.String(s.variable), " ")
values = removeDefault(values)
propStruct := values.Elem()
if !propStruct.IsValid() {
return nil, nil
}
if err := s.printfIntoPropertyRecursive(nil, propStruct, configValues); err != nil {
return nil, err
}
return values.Interface(), nil
}
func (s *listVariable) printfIntoPropertyRecursive(fieldName []string, propStruct reflect.Value, configValues []string) error {
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()
kind = field.Kind()
}
switch kind {
case reflect.Slice:
elemType := field.Type().Elem()
newLen := field.Len() * len(configValues)
newField := reflect.MakeSlice(field.Type(), 0, newLen)
for j := 0; j < field.Len(); j++ {
for _, configValue := range configValues {
res := reflect.Indirect(reflect.New(elemType))
res.Set(field.Index(j))
err := printfIntoProperty(res, configValue)
if err != nil {
fieldName = append(fieldName, propStruct.Type().Field(i).Name)
return fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, strings.Join(fieldName, "."), err)
}
newField = reflect.Append(newField, res)
}
}
field.Set(newField)
case reflect.Struct:
fieldName = append(fieldName, propStruct.Type().Field(i).Name)
if err := s.printfIntoPropertyRecursive(fieldName, field, configValues); err != nil {
return err
}
fieldName = fieldName[:len(fieldName)-1]
default:
fieldName = append(fieldName, propStruct.Type().Field(i).Name)
return fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, strings.Join(fieldName, "."), kind)
}
}
return nil
}
func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
s := propertyValue.String()
@ -739,7 +839,7 @@ func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
}
if count > 1 {
return fmt.Errorf("value variable properties only support a single '%%'")
return fmt.Errorf("list/value variable properties only support a single '%%'")
}
if !strings.Contains(s, "%s") {

View file

@ -291,11 +291,13 @@ func Test_createAffectablePropertiesType(t *testing.T) {
type properties struct {
A *string
B bool
C []string
}
type boolVarProps struct {
type varProps struct {
A *string
B bool
C []string
Conditions_default *properties
}
@ -311,6 +313,19 @@ type valueSoongConfigVars struct {
My_value_var interface{}
}
type listProperties struct {
C []string
}
type listVarProps struct {
C []string
Conditions_default *listProperties
}
type listSoongConfigVars struct {
List_var interface{}
}
func Test_PropertiesToApply_Bool(t *testing.T) {
mt, _ := newModuleType(&ModuleTypeProperties{
Module_type: "foo",
@ -330,7 +345,7 @@ func Test_PropertiesToApply_Bool(t *testing.T) {
Soong_config_variables boolSoongConfigVars
}{
Soong_config_variables: boolSoongConfigVars{
Bool_var: &boolVarProps{
Bool_var: &varProps{
A: boolVarPositive.A,
B: boolVarPositive.B,
Conditions_default: conditionsDefault,
@ -373,6 +388,59 @@ func Test_PropertiesToApply_Bool(t *testing.T) {
}
}
func Test_PropertiesToApply_List(t *testing.T) {
mt, _ := newModuleType(&ModuleTypeProperties{
Module_type: "foo",
Config_namespace: "bar",
List_variables: []string{"my_list_var"},
Properties: []string{"c"},
})
conditionsDefault := &listProperties{
C: []string{"default"},
}
actualProps := &struct {
Soong_config_variables listSoongConfigVars
}{
Soong_config_variables: listSoongConfigVars{
List_var: &listVarProps{
C: []string{"A=%s", "B=%s"},
Conditions_default: conditionsDefault,
},
},
}
props := reflect.ValueOf(actualProps)
testCases := []struct {
name string
config SoongConfig
wantProps []interface{}
}{
{
name: "no_vendor_config",
config: Config(map[string]string{}),
wantProps: []interface{}{conditionsDefault},
},
{
name: "value_var_set",
config: Config(map[string]string{"my_list_var": "hello there"}),
wantProps: []interface{}{&listProperties{
C: []string{"A=hello", "A=there", "B=hello", "B=there"},
}},
},
}
for _, tc := range testCases {
gotProps, err := PropertiesToApply(mt, props, tc.config)
if err != nil {
t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
}
if !reflect.DeepEqual(gotProps, tc.wantProps) {
t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
}
}
}
func Test_PropertiesToApply_Value(t *testing.T) {
mt, _ := newModuleType(&ModuleTypeProperties{
Module_type: "foo",
@ -388,7 +456,7 @@ func Test_PropertiesToApply_Value(t *testing.T) {
Soong_config_variables valueSoongConfigVars
}{
Soong_config_variables: valueSoongConfigVars{
My_value_var: &boolVarProps{
My_value_var: &varProps{
A: proptools.StringPtr("A=%s"),
B: true,
Conditions_default: conditionsDefault,
@ -524,7 +592,7 @@ func Test_PropertiesToApply_String_Error(t *testing.T) {
Soong_config_variables stringSoongConfigVars
}{
Soong_config_variables: stringSoongConfigVars{
String_var: &boolVarProps{
String_var: &varProps{
A: stringVarPositive.A,
B: stringVarPositive.B,
Conditions_default: conditionsDefault,