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,