Merge "Add support to Soong config list variable" into main
This commit is contained in:
commit
d47220d470
4 changed files with 266 additions and 58 deletions
26
README.md
26
README.md
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue