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
This commit is contained in:
Colin Cross 2015-07-09 13:57:48 -07:00
parent 8f301d583f
commit 7f64b6de31
7 changed files with 375 additions and 114 deletions

View file

@ -97,10 +97,12 @@ bootstrap_go_package {
"common/config.go", "common/config.go",
"common/defs.go", "common/defs.go",
"common/env.go", "common/env.go",
"common/extend.go",
"common/glob.go", "common/glob.go",
"common/module.go", "common/module.go",
"common/paths.go", "common/paths.go",
"common/util.go", "common/util.go",
"common/variable.go",
], ],
} }

View file

@ -175,6 +175,44 @@ func translateTargetConditionals(props []*bpparser.Property,
return return
} }
func translateProductVariableConditionals(props []*bpparser.Property) (computedProps []string, err error) {
for _, productVariable := range props {
v, ok := productVariableConditionals[productVariable.Name.Name]
if !ok {
return nil, fmt.Errorf("Unsupported product variable %q", productVariable.Name.Name)
}
var scopedProps []string
for _, conditionalScopedProp := range productVariable.Value.MapValue {
if assignment, ok, err := translateSingleProperty(conditionalScopedProp); err != nil {
return nil, err
} else if ok {
assignment.assigner = "+="
a := assignment.assignment()
if v.value != "" && strings.Contains(a, "%d") {
a = strings.Replace(a, "%d", v.value, 1)
}
scopedProps = append(scopedProps, a)
} else {
return nil, fmt.Errorf("Unsupported product variable property %q",
conditionalScopedProp.Name.Name)
}
}
if len(scopedProps) > 0 {
if v.conditional != "" {
computedProps = append(computedProps, v.conditional)
computedProps = append(computedProps, scopedProps...)
computedProps = append(computedProps, "endif")
} else {
computedProps = append(computedProps, scopedProps...)
}
}
}
return computedProps, nil
}
var secondTargetReplacer = strings.NewReplacer("TARGET_", "TARGET_2ND_") var secondTargetReplacer = strings.NewReplacer("TARGET_", "TARGET_2ND_")
func translateSuffixProperties(suffixProps []*bpparser.Property, func translateSuffixProperties(suffixProps []*bpparser.Property,
@ -303,6 +341,12 @@ func (w *androidMkWriter) parsePropsAndWriteModule(module *Module) error {
return err return err
} }
standardProps = append(standardProps, props...) standardProps = append(standardProps, props...)
} else if "product_variables" == prop.Name.Name {
props, err := translateProductVariableConditionals(prop.Value.MapValue)
if err != nil {
return err
}
standardProps = append(standardProps, props...)
} else if _, ok := ignoredProperties[prop.Name.Name]; ok { } else if _, ok := ignoredProperties[prop.Name.Name]; ok {
} else { } else {
return fmt.Errorf("Unsupported property %q", prop.Name.Name) return fmt.Errorf("Unsupported property %q", prop.Name.Name)

View file

@ -172,3 +172,10 @@ var targetToHostModuleRule = map[string]string{
"BUILD_NATIVE_TEST": "BUILD_HOST_NATIVE_TEST", "BUILD_NATIVE_TEST": "BUILD_HOST_NATIVE_TEST",
"BUILD_JAVA_LIBRARY": "BUILD_HOST_JAVA_LIBRARY", "BUILD_JAVA_LIBRARY": "BUILD_HOST_JAVA_LIBRARY",
} }
var productVariableConditionals = map[string]struct{conditional, value string}{
"device_uses_jemalloc": {"ifneq ($(MALLOC_IMPL),dlmalloc)", ""},
"device_uses_dlmalloc": {"ifeq ($(MALLOC_IMPL),dlmalloc)", ""},
"device_uses_logd": {"ifneq ($(TARGET_USES_LOGD),false)", ""},
"dlmalloc_alignment": {"ifdef DLMALLOC_ALIGNMENT", "$(DLMALLOC_ALIGNMENT)"},
}

View file

@ -450,8 +450,12 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
return return
} }
callback := func(srcPropertyName, dstPropertyName string) {
a.extendedProperties[dstPropertyName] = struct{}{}
}
for i := range a.generalProperties { for i := range a.generalProperties {
generalPropsValue := reflect.ValueOf(a.generalProperties[i]).Elem() generalPropsValue := []reflect.Value{reflect.ValueOf(a.generalProperties[i]).Elem()}
// Handle arch-specific properties in the form: // Handle arch-specific properties in the form:
// arch: { // arch: {
@ -461,8 +465,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
// }, // },
t := arch.ArchType t := arch.ArchType
field := proptools.FieldNameForProperty(t.Name) field := proptools.FieldNameForProperty(t.Name)
a.extendProperties(ctx, "arch", t.Name, generalPropsValue, extendProperties(ctx, "arch_variant", "arch."+t.Name, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
// Handle arch-variant-specific properties in the form: // Handle arch-variant-specific properties in the form:
// arch: { // arch: {
@ -473,8 +477,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
v := dashToUnderscoreReplacer.Replace(arch.ArchVariant) v := dashToUnderscoreReplacer.Replace(arch.ArchVariant)
if v != "" { if v != "" {
field := proptools.FieldNameForProperty(v) field := proptools.FieldNameForProperty(v)
a.extendProperties(ctx, "arch", v, generalPropsValue, extendProperties(ctx, "arch_variant", "arch."+v, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
} }
// Handle cpu-variant-specific properties in the form: // Handle cpu-variant-specific properties in the form:
@ -486,8 +490,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
c := dashToUnderscoreReplacer.Replace(arch.CpuVariant) c := dashToUnderscoreReplacer.Replace(arch.CpuVariant)
if c != "" { if c != "" {
field := proptools.FieldNameForProperty(c) field := proptools.FieldNameForProperty(c)
a.extendProperties(ctx, "arch", c, generalPropsValue, extendProperties(ctx, "arch_variant", "arch."+c, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
} }
// Handle multilib-specific properties in the form: // Handle multilib-specific properties in the form:
@ -497,8 +501,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
// }, // },
// }, // },
multilibField := proptools.FieldNameForProperty(t.Multilib) multilibField := proptools.FieldNameForProperty(t.Multilib)
a.extendProperties(ctx, "multilib", t.Multilib, generalPropsValue, extendProperties(ctx, "arch_variant", "multilib."+t.Multilib, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(multilibField).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(multilibField).Elem().Elem(), callback)
// Handle host-or-device-specific properties in the form: // Handle host-or-device-specific properties in the form:
// target: { // target: {
@ -508,8 +512,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
// }, // },
hodProperty := hod.Property() hodProperty := hod.Property()
hodField := proptools.FieldNameForProperty(hodProperty) hodField := proptools.FieldNameForProperty(hodProperty)
a.extendProperties(ctx, "target", hodProperty, generalPropsValue, extendProperties(ctx, "arch_variant", "target."+hodProperty, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName(hodField).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName(hodField).Elem().Elem(), callback)
// Handle host target properties in the form: // Handle host target properties in the form:
// target: { // target: {
@ -538,15 +542,15 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
if hod.Host() { if hod.Host() {
for _, v := range osList { for _, v := range osList {
if v.goos == runtime.GOOS { if v.goos == runtime.GOOS {
a.extendProperties(ctx, "target", v.goos, generalPropsValue, extendProperties(ctx, "arch_variant", "target."+v.goos, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem(), callback)
t := arch.ArchType t := arch.ArchType
a.extendProperties(ctx, "target", v.goos+"_"+t.Name, generalPropsValue, extendProperties(ctx, "arch_variant", "target."+v.goos+"_"+t.Name, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field+"_"+t.Name).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field+"_"+t.Name).Elem().Elem(), callback)
} }
} }
a.extendProperties(ctx, "target", "not_windows", generalPropsValue, extendProperties(ctx, "arch_variant", "target.not_windows", generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem(), callback)
} }
// Handle 64-bit device properties in the form: // Handle 64-bit device properties in the form:
@ -564,11 +568,11 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
// debuggerd that need to know when they are a 32-bit process running on a 64-bit device // debuggerd that need to know when they are a 32-bit process running on a 64-bit device
if hod.Device() { if hod.Device() {
if true /* && target_is_64_bit */ { if true /* && target_is_64_bit */ {
a.extendProperties(ctx, "target", "android64", generalPropsValue, extendProperties(ctx, "arch_variant", "target.android64", generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android64").Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android64").Elem().Elem(), callback)
} else { } else {
a.extendProperties(ctx, "target", "android32", generalPropsValue, extendProperties(ctx, "arch_variant", "target.android32", generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android32").Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android32").Elem().Elem(), callback)
} }
} }
@ -583,8 +587,8 @@ func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext)
// }, // },
if hod.Device() { if hod.Device() {
t := arch.ArchType t := arch.ArchType
a.extendProperties(ctx, "target", "android_"+t.Name, generalPropsValue, extendProperties(ctx, "arch_variant", "target.android_"+t.Name, generalPropsValue,
reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android_"+t.Name).Elem().Elem()) reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android_"+t.Name).Elem().Elem(), callback)
} }
if ctx.Failed() { if ctx.Failed() {
@ -607,93 +611,3 @@ func forEachInterface(v reflect.Value, f func(reflect.Value)) {
panic(fmt.Errorf("Unsupported kind %s", v.Kind())) panic(fmt.Errorf("Unsupported kind %s", v.Kind()))
} }
} }
// TODO: move this to proptools
func (a *AndroidModuleBase) extendProperties(ctx blueprint.EarlyMutatorContext, variationType, variationName string,
dstValue, srcValue reflect.Value) {
a.extendPropertiesRecursive(ctx, variationType, variationName, dstValue, srcValue, "")
}
func (a *AndroidModuleBase) extendPropertiesRecursive(ctx blueprint.EarlyMutatorContext, variationType, variationName string,
dstValue, srcValue reflect.Value, recursePrefix string) {
typ := dstValue.Type()
if srcValue.Type() != typ {
panic(fmt.Errorf("can't extend mismatching types (%s <- %s)",
dstValue.Kind(), srcValue.Kind()))
}
for i := 0; i < srcValue.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
// The field is not exported so just skip it.
continue
}
srcFieldValue := srcValue.Field(i)
dstFieldValue := dstValue.Field(i)
localPropertyName := proptools.PropertyNameForField(field.Name)
propertyName := fmt.Sprintf("%s.%s.%s%s", variationType, variationName,
recursePrefix, localPropertyName)
propertyPresentInVariation := ctx.ContainsProperty(propertyName)
if !propertyPresentInVariation {
continue
}
tag := field.Tag.Get("android")
tags := map[string]bool{}
for _, entry := range strings.Split(tag, ",") {
if entry != "" {
tags[entry] = true
}
}
if !tags["arch_variant"] {
ctx.PropertyErrorf(propertyName, "property %q can't be specific to a build variant",
recursePrefix+proptools.PropertyNameForField(field.Name))
continue
}
if !ctx.ContainsProperty(propertyName) {
continue
}
a.extendedProperties[localPropertyName] = struct{}{}
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.Struct:
// Recursively extend the struct's fields.
newRecursePrefix := fmt.Sprintf("%s%s.", recursePrefix, strings.ToLower(field.Name))
a.extendPropertiesRecursive(ctx, variationType, variationName,
dstFieldValue, srcFieldValue,
newRecursePrefix)
case reflect.Slice:
dstFieldValue.Set(reflect.AppendSlice(dstFieldValue, srcFieldValue))
case reflect.Ptr, reflect.Interface:
// Recursively extend the pointed-to struct's fields.
if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
panic(fmt.Errorf("can't extend field %q: nilitude mismatch"))
}
if dstFieldValue.Type() != srcFieldValue.Type() {
panic(fmt.Errorf("can't extend field %q: type mismatch"))
}
if !dstFieldValue.IsNil() {
newRecursePrefix := fmt.Sprintf("%s.%s", recursePrefix, field.Name)
a.extendPropertiesRecursive(ctx, variationType, variationName,
dstFieldValue.Elem(), srcFieldValue.Elem(),
newRecursePrefix)
}
default:
panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
field.Name, srcFieldValue.Kind()))
}
}
}

152
common/extend.go Normal file
View file

@ -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")
}
}
}

View file

@ -125,7 +125,7 @@ func InitAndroidModule(m AndroidModule,
base.module = m base.module = m
base.extendedProperties = make(map[string]struct{}) base.extendedProperties = make(map[string]struct{})
propertyStructs = append(propertyStructs, &base.commonProperties) propertyStructs = append(propertyStructs, &base.commonProperties, &base.variableProperties)
return m, propertyStructs return m, propertyStructs
} }
@ -194,6 +194,7 @@ type AndroidModuleBase struct {
module AndroidModule module AndroidModule
commonProperties commonProperties commonProperties commonProperties
variableProperties variableProperties
hostAndDeviceProperties hostAndDeviceProperties hostAndDeviceProperties hostAndDeviceProperties
generalProperties []interface{} generalProperties []interface{}
archProperties []*archProperties archProperties []*archProperties

141
common/variable.go Normal file
View file

@ -0,0 +1,141 @@
// 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"
"android/soong"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
func init() {
soong.RegisterEarlyMutator("variable", VariableMutator)
}
type variableProperties struct {
Product_variables struct {
Device_uses_logd struct {
Cflags []string
Srcs []string
}
Device_uses_dlmalloc struct {
Cflags []string
Srcs []string
}
Device_uses_jemalloc struct {
Cflags []string
Srcs []string
Whole_static_libs []string
Include_dirs []string
}
Dlmalloc_alignment struct {
Cflags []string
}
}
}
var zeroProductVariables variableProperties
// TODO: replace hardcoded test values with per-product values
var productVariables = map[string]interface{}{
"device_uses_logd": true,
"device_uses_jemalloc": true,
}
func VariableMutator(mctx blueprint.EarlyMutatorContext) {
var module AndroidModule
var ok bool
if module, ok = mctx.Module().(AndroidModule); !ok {
return
}
// TODO: depend on config variable, create variants, propagate variants up tree
a := module.base()
variableValues := reflect.ValueOf(a.variableProperties.Product_variables)
zeroValues := reflect.ValueOf(zeroProductVariables.Product_variables)
for i := 0; i < variableValues.NumField(); i++ {
variableValue := variableValues.Field(i)
zeroValue := zeroValues.Field(i)
if reflect.DeepEqual(variableValue, zeroValue) {
continue
}
name := proptools.PropertyNameForField(variableValues.Type().Field(i).Name)
property := "product_variables." + name
val := productVariables[name]
if mctx.ContainsProperty(property) && val != nil {
a.setVariableProperties(mctx, property, variableValue, val)
}
}
}
func (a *AndroidModuleBase) setVariableProperties(ctx blueprint.EarlyMutatorContext,
prefix string, productVariablePropertyValue reflect.Value, variableValue interface{}) {
generalPropertyValues := make([]reflect.Value, len(a.generalProperties))
for i := range a.generalProperties {
generalPropertyValues[i] = reflect.ValueOf(a.generalProperties[i]).Elem()
}
if variableValue != nil {
printfIntoProperties(productVariablePropertyValue, variableValue)
}
extendProperties(ctx, "", prefix, generalPropertyValues, productVariablePropertyValue, nil)
}
func printfIntoProperties(productVariablePropertyValue reflect.Value, variableValue interface{}) {
for i := 0; i < productVariablePropertyValue.NumField(); i++ {
propertyValue := productVariablePropertyValue.Field(i)
switch propertyValue.Kind() {
case reflect.String:
printfIntoProperty(propertyValue, variableValue)
case reflect.Slice:
for j := 0; j < propertyValue.Len(); j++ {
printfIntoProperty(propertyValue.Index(j), variableValue)
}
case reflect.Struct:
printfIntoProperties(propertyValue, variableValue)
default:
panic(fmt.Errorf("unsupported field kind %q", propertyValue.Kind()))
}
}
}
func printfIntoProperty(propertyValue reflect.Value, variableValue interface{}) {
s := propertyValue.String()
// For now, we only support int formats
var i int
if strings.Contains(s, "%d") {
switch v := variableValue.(type) {
case int:
i = v
case bool:
if v {
i = 1
}
default:
panic(fmt.Errorf("unsupported type %T", variableValue))
}
propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, i)))
}
}