Merge "soong config: add value_variable substitution"
diff --git a/README.md b/README.md
index 3eac87b..8b028a8 100644
--- a/README.md
+++ b/README.md
@@ -421,6 +421,7 @@
     config_namespace: "acme",
     variables: ["board"],
     bool_variables: ["feature"],
+    value_variables: ["width"],
     properties: ["cflags", "srcs"],
 }
 
@@ -431,8 +432,9 @@
 ```
 
 This example describes a new `acme_cc_defaults` module type that extends the
-`cc_defaults` module type, with two additional conditionals based on variables
-`board` and `feature`, which can affect properties `cflags` and `srcs`.
+`cc_defaults` module type, with three additional conditionals based on
+variables `board`, `feature` and `width`, which can affect properties `cflags`
+and `srcs`.
 
 The values of the variables can be set from a product's `BoardConfig.mk` file:
 ```
@@ -443,6 +445,7 @@
 
 SOONG_CONFIG_acme_board := soc_a
 SOONG_CONFIG_acme_feature := true
+SOONG_CONFIG_acme_width := 200
 ```
 
 The `acme_cc_defaults` module type can be used anywhere after the definition in
@@ -471,6 +474,9 @@
         feature: {
             cflags: ["-DFEATURE"],
         },
+        width: {
+            cflags: ["-DWIDTH=%s"],
+        },
     },
 }
 
@@ -482,7 +488,7 @@
 ```
 
 With the `BoardConfig.mk` snippet above, libacme_foo would build with
-cflags "-DGENERIC -DSOC_A -DFEATURE".
+cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
 
 `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 6ce609a..619cf86 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -73,6 +73,9 @@
 //             feature: {
 //                 cflags: ["-DFEATURE"],
 //             },
+//             width: {
+//                 cflags: ["-DWIDTH=%s"],
+//             },
 //         },
 //     }
 //
@@ -90,6 +93,7 @@
 //         config_namespace: "acme",
 //         variables: ["board"],
 //         bool_variables: ["feature"],
+//         value_variables: ["width"],
 //         properties: ["cflags", "srcs"],
 //     }
 //
@@ -107,8 +111,9 @@
 //
 //     SOONG_CONFIG_acme_board := soc_a
 //     SOONG_CONFIG_acme_feature := true
+//     SOONG_CONFIG_acme_width := 200
 //
-// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
 func soongConfigModuleTypeImportFactory() Module {
 	module := &soongConfigModuleTypeImport{}
 
@@ -151,6 +156,7 @@
 //         config_namespace: "acme",
 //         variables: ["board"],
 //         bool_variables: ["feature"],
+//         value_variables: ["width"],
 //         properties: ["cflags", "srcs"],
 //     }
 //
@@ -174,6 +180,9 @@
 //             feature: {
 //                 cflags: ["-DFEATURE"],
 //             },
+//             width: {
+//	               cflags: ["-DWIDTH=%s"],
+//             },
 //         },
 //     }
 //
@@ -192,6 +201,7 @@
 //
 //     SOONG_CONFIG_acme_board := soc_a
 //     SOONG_CONFIG_acme_feature := true
+//     SOONG_CONFIG_acme_width := 200
 //
 // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
 func soongConfigModuleTypeFactory() Module {
@@ -352,7 +362,12 @@
 
 			AddLoadHook(module, func(ctx LoadHookContext) {
 				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
-				for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
+				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
+				if err != nil {
+					ctx.ModuleErrorf("%s", err)
+					return
+				}
+				for _, ps := range newProps {
 					ctx.AppendProperties(ps)
 				}
 			})
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 1cf060d..f905b1a 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -45,6 +45,7 @@
 			config_namespace: "acme",
 			variables: ["board", "feature1", "FEATURE3"],
 			bool_variables: ["feature2"],
+			value_variables: ["size"],
 			properties: ["cflags", "srcs"],
 		}
 
@@ -82,6 +83,9 @@
 						cflags: ["-DSOC_B"],
 					},
 				},
+				size: {
+					cflags: ["-DSIZE=%s"],
+				},
 				feature1: {
 					cflags: ["-DFEATURE1"],
 				},
@@ -101,6 +105,7 @@
 		config.TestProductVariables.VendorVars = map[string]map[string]string{
 			"acme": map[string]string{
 				"board":    "soc_a",
+				"size":     "42",
 				"feature1": "true",
 				"feature2": "false",
 				// FEATURE3 unset
@@ -121,7 +126,7 @@
 		FailIfErrored(t, errs)
 
 		foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
-		if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
+		if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSIZE=42", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
 			t.Errorf("wanted foo cflags %q, got %q", w, g)
 		}
 	}
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 2d6063d..142a813 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -112,6 +112,10 @@
 	// the list of boolean SOONG_CONFIG variables that this module type will read
 	Bool_variables []string
 
+	// the list of SOONG_CONFIG variables that this module type will read. The value will be
+	// inserted into the properties with %s substitution.
+	Value_variables []string
+
 	// the list of properties that this module type will extend.
 	Properties []string
 }
@@ -161,6 +165,18 @@
 		})
 	}
 
+	for _, name := range props.Value_variables {
+		if name == "" {
+			return []error{fmt.Errorf("value_variables entry must not be blank")}
+		}
+
+		mt.Variables = append(mt.Variables, &valueVariable{
+			baseVariable: baseVariable{
+				variable: name,
+			},
+		})
+	}
+
 	return nil
 }
 
@@ -404,15 +420,17 @@
 
 // PropertiesToApply returns the applicable properties from a ModuleType that should be applied
 // based on SoongConfig values.
-func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) []interface{} {
+func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
 	var ret []interface{}
 	props = props.Elem().FieldByName(soongConfigProperty)
 	for i, c := range moduleType.Variables {
-		if ps := c.PropertiesToApply(config, props.Field(i)); ps != nil {
+		if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
+			return nil, err
+		} else if ps != nil {
 			ret = append(ret, ps)
 		}
 	}
-	return ret
+	return ret, nil
 }
 
 type ModuleType struct {
@@ -438,7 +456,7 @@
 
 	// PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
 	// to the module.
-	PropertiesToApply(config SoongConfig, values reflect.Value) interface{}
+	PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
 }
 
 type baseVariable struct {
@@ -473,14 +491,14 @@
 	}
 }
 
-func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
 	for j, v := range s.values {
 		if config.String(s.variable) == v {
-			return values.Field(j).Interface()
+			return values.Field(j).Interface(), nil
 		}
 	}
 
-	return nil
+	return nil, nil
 }
 
 type boolVariable struct {
@@ -495,11 +513,83 @@
 	v.Set(reflect.Zero(typ))
 }
 
-func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
 	if config.Bool(b.variable) {
-		return values.Interface()
+		return values.Interface(), nil
 	}
 
+	return nil, nil
+}
+
+type valueVariable struct {
+	baseVariable
+}
+
+func (s *valueVariable) variableValuesType() reflect.Type {
+	return emptyInterfaceType
+}
+
+func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	v.Set(reflect.Zero(typ))
+}
+
+func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
+	if !config.IsSet(s.variable) {
+		return nil, nil
+	}
+	configValue := config.String(s.variable)
+
+	propStruct := values.Elem().Elem()
+	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()
+		}
+		switch kind {
+		case reflect.String:
+			err := printfIntoProperty(field, configValue)
+			if err != nil {
+				return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
+			}
+		case reflect.Slice:
+			for j := 0; j < field.Len(); j++ {
+				err := printfIntoProperty(field.Index(j), configValue)
+				if err != nil {
+					return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
+				}
+			}
+		case reflect.Bool:
+			// Nothing to do
+		default:
+			return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
+		}
+	}
+
+	return values.Interface(), nil
+}
+
+func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
+	s := propertyValue.String()
+
+	count := strings.Count(s, "%")
+	if count == 0 {
+		return nil
+	}
+
+	if count > 1 {
+		return fmt.Errorf("value variable properties only support a single '%%'")
+	}
+
+	if !strings.Contains(s, "%s") {
+		return fmt.Errorf("unsupported %% in value variable property")
+	}
+
+	propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
+
 	return nil
 }