diff --git a/README.md b/README.md index 93260e618..42fecb4d4 100644 --- a/README.md +++ b/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 diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go index 90b49eb19..38db92995 100644 --- a/android/soong_config_modules.go +++ b/android/soong_config_modules.go @@ -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{} diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go index c78b72669..c910974f6 100644 --- a/android/soongconfig/modules.go +++ b/android/soongconfig/modules.go @@ -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") { diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go index 1da0b49ad..d76794ca5 100644 --- a/android/soongconfig/modules_test.go +++ b/android/soongconfig/modules_test.go @@ -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,