Support product variables

Allow modules to vary their properties based on product variables.
For now, DEVICE_USES_LOGD, DEVICE_USES_JEMALLOC, and DEVICE_USES_DLMALLOC,
and BOARD_MALLOC_ALIGNMENT are supported.

Product variables can provide a value (only bool and int supported for
now), and if any of the product variable properties contains a "%d"
then Sprintf will be called with the property value as the format
and the product variable value convert to an int as the only argument.

For example:

    product_variables: {
        dlmalloc_alignment: {
            cflags: ["-DMALLOC_ALIGNMENT=%d"],
        },
    },

will cause -DMALLOC_ALIGNMENT=16 to be added to any top level
properties called "cflags".

Change-Id: I74882a6ab4914d3e222f8d06cfac371b7b829ae5
diff --git a/common/extend.go b/common/extend.go
new file mode 100644
index 0000000..f2d061b
--- /dev/null
+++ b/common/extend.go
@@ -0,0 +1,152 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package common
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+// TODO: move this to proptools
+func extendProperties(ctx blueprint.EarlyMutatorContext,
+	requiredTag, srcPrefix string, dstValues []reflect.Value, srcValue reflect.Value,
+	callback func(string, string)) {
+	if srcPrefix != "" {
+		srcPrefix += "."
+	}
+	extendPropertiesRecursive(ctx, requiredTag, srcValue, dstValues, srcPrefix, "", callback)
+}
+
+func extendPropertiesRecursive(ctx blueprint.EarlyMutatorContext, requiredTag string,
+	srcValue reflect.Value, dstValues []reflect.Value, srcPrefix, dstPrefix string,
+	callback func(string, string)) {
+
+	typ := srcValue.Type()
+	for i := 0; i < srcValue.NumField(); i++ {
+		srcField := typ.Field(i)
+		if srcField.PkgPath != "" {
+			// The field is not exported so just skip it.
+			continue
+		}
+
+		localPropertyName := proptools.PropertyNameForField(srcField.Name)
+		srcPropertyName := srcPrefix + localPropertyName
+		srcFieldValue := srcValue.Field(i)
+
+		if !ctx.ContainsProperty(srcPropertyName) {
+			continue
+		}
+
+		found := false
+		for _, dstValue := range dstValues {
+			dstField, ok := dstValue.Type().FieldByName(srcField.Name)
+			if !ok {
+				continue
+			}
+
+			dstFieldValue := dstValue.FieldByIndex(dstField.Index)
+
+			if srcFieldValue.Type() != dstFieldValue.Type() {
+				panic(fmt.Errorf("can't extend mismatching types for %q (%s <- %s)",
+					srcPropertyName, dstFieldValue.Type(), srcFieldValue.Type()))
+			}
+
+			dstPropertyName := dstPrefix + localPropertyName
+
+			if requiredTag != "" {
+				tag := dstField.Tag.Get("android")
+				tags := map[string]bool{}
+				for _, entry := range strings.Split(tag, ",") {
+					if entry != "" {
+						tags[entry] = true
+					}
+				}
+
+				if !tags[requiredTag] {
+					ctx.PropertyErrorf(srcPropertyName, "property can't be specific to a build variant")
+					continue
+				}
+			}
+
+			if callback != nil {
+				callback(srcPropertyName, dstPropertyName)
+			}
+
+			found = true
+
+			switch srcFieldValue.Kind() {
+			case reflect.Bool:
+				// Replace the original value.
+				dstFieldValue.Set(srcFieldValue)
+			case reflect.String:
+				// Append the extension string.
+				dstFieldValue.SetString(dstFieldValue.String() +
+					srcFieldValue.String())
+			case reflect.Slice:
+				dstFieldValue.Set(reflect.AppendSlice(dstFieldValue, srcFieldValue))
+			case reflect.Interface:
+				if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
+					panic(fmt.Errorf("can't extend field %q: nilitude mismatch", srcPropertyName))
+				}
+				if dstFieldValue.IsNil() {
+					continue
+				}
+
+				dstFieldValue = dstFieldValue.Elem()
+				srcFieldValue = srcFieldValue.Elem()
+
+				if dstFieldValue.Type() != srcFieldValue.Type() {
+					panic(fmt.Errorf("can't extend field %q: type mismatch", srcPropertyName))
+				}
+				if srcFieldValue.Kind() != reflect.Ptr {
+					panic(fmt.Errorf("can't extend field %q: interface not a pointer", srcPropertyName))
+				}
+				fallthrough
+			case reflect.Ptr:
+				if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
+					panic(fmt.Errorf("can't extend field %q: nilitude mismatch", srcPropertyName))
+				}
+				if dstFieldValue.IsNil() {
+					continue
+				}
+
+				dstFieldValue = dstFieldValue.Elem()
+				srcFieldValue = srcFieldValue.Elem()
+
+				if dstFieldValue.Type() != srcFieldValue.Type() {
+					panic(fmt.Errorf("can't extend field %q: type mismatch", srcPropertyName))
+				}
+				if srcFieldValue.Kind() != reflect.Struct {
+					panic(fmt.Errorf("can't extend field %q: pointer not to a struct", srcPropertyName))
+				}
+				fallthrough
+			case reflect.Struct:
+				// Recursively extend the struct's fields.
+				extendPropertiesRecursive(ctx, requiredTag, srcFieldValue, []reflect.Value{dstFieldValue},
+					srcPropertyName+".", srcPropertyName+".", callback)
+			default:
+				panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
+					srcPropertyName, srcFieldValue.Kind()))
+			}
+		}
+		if !found {
+			ctx.PropertyErrorf(srcPropertyName, "failed to find property to extend")
+		}
+	}
+}