Add support to Soong config list variable
List variables act similar to value variables. Each value in a list
variable will be added to a list property after string substitution.
Bug: 329208946
Test: m --no-skip-soong-tests
Test: m selinux_policy_system_soong
Change-Id: I1127bfb0798e7e5f7d665f647307224d5ff5d790
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 90b49eb..38db929 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -64,6 +64,7 @@
// 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 @@
// cflags: ["-DWIDTH=DEFAULT"],
// },
// },
+// impl: {
+// srcs: ["impl/%s"],
+// conditions_default: {
+// srcs: ["impl/default.cpp"],
+// },
+// },
// },
// }
//
@@ -122,6 +129,7 @@
// variables: ["board"],
// bool_variables: ["feature"],
// value_variables: ["width"],
+// list_variables: ["impl"],
// properties: ["cflags", "srcs"],
// }
//
@@ -135,8 +143,10 @@
// $(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 @@
// 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 @@
// 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 @@
//
// 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 @@
//
// 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 @@
// 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 c78b726..c910974 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -117,6 +117,10 @@
// 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 @@
})
}
+ 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 @@
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 @@
}
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 1da0b49..d76794c 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -291,11 +291,13 @@
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 @@
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 @@
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_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 @@
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 @@
Soong_config_variables stringSoongConfigVars
}{
Soong_config_variables: stringSoongConfigVars{
- String_var: &boolVarProps{
+ String_var: &varProps{
A: stringVarPositive.A,
B: stringVarPositive.B,
Conditions_default: conditionsDefault,