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/Android.bp b/Android.bp
index f001246..404c478 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,10 +97,12 @@
"common/config.go",
"common/defs.go",
"common/env.go",
+ "common/extend.go",
"common/glob.go",
"common/module.go",
"common/paths.go",
"common/util.go",
+ "common/variable.go",
],
}
diff --git a/androidbp/cmd/androidbp.go b/androidbp/cmd/androidbp.go
index b1e364c..3edd243 100644
--- a/androidbp/cmd/androidbp.go
+++ b/androidbp/cmd/androidbp.go
@@ -175,6 +175,44 @@
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_")
func translateSuffixProperties(suffixProps []*bpparser.Property,
@@ -303,6 +341,12 @@
return err
}
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 {
return fmt.Errorf("Unsupported property %q", prop.Name.Name)
diff --git a/androidbp/cmd/soong.go b/androidbp/cmd/soong.go
index 1dffe4a..e9d421a 100644
--- a/androidbp/cmd/soong.go
+++ b/androidbp/cmd/soong.go
@@ -172,3 +172,10 @@
"BUILD_NATIVE_TEST": "BUILD_HOST_NATIVE_TEST",
"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)"},
+}
diff --git a/common/arch.go b/common/arch.go
index 3d685e5..b9e3a5c 100644
--- a/common/arch.go
+++ b/common/arch.go
@@ -450,8 +450,12 @@
return
}
+ callback := func(srcPropertyName, dstPropertyName string) {
+ a.extendedProperties[dstPropertyName] = struct{}{}
+ }
+
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:
// arch: {
@@ -461,8 +465,8 @@
// },
t := arch.ArchType
field := proptools.FieldNameForProperty(t.Name)
- a.extendProperties(ctx, "arch", t.Name, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "arch."+t.Name, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
// Handle arch-variant-specific properties in the form:
// arch: {
@@ -473,8 +477,8 @@
v := dashToUnderscoreReplacer.Replace(arch.ArchVariant)
if v != "" {
field := proptools.FieldNameForProperty(v)
- a.extendProperties(ctx, "arch", v, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "arch."+v, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
}
// Handle cpu-variant-specific properties in the form:
@@ -486,8 +490,8 @@
c := dashToUnderscoreReplacer.Replace(arch.CpuVariant)
if c != "" {
field := proptools.FieldNameForProperty(c)
- a.extendProperties(ctx, "arch", c, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "arch."+c, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Arch).FieldByName(field).Elem().Elem(), callback)
}
// Handle multilib-specific properties in the form:
@@ -497,8 +501,8 @@
// },
// },
multilibField := proptools.FieldNameForProperty(t.Multilib)
- a.extendProperties(ctx, "multilib", t.Multilib, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(multilibField).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "multilib."+t.Multilib, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(multilibField).Elem().Elem(), callback)
// Handle host-or-device-specific properties in the form:
// target: {
@@ -508,8 +512,8 @@
// },
hodProperty := hod.Property()
hodField := proptools.FieldNameForProperty(hodProperty)
- a.extendProperties(ctx, "target", hodProperty, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName(hodField).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target."+hodProperty, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName(hodField).Elem().Elem(), callback)
// Handle host target properties in the form:
// target: {
@@ -538,15 +542,15 @@
if hod.Host() {
for _, v := range osList {
if v.goos == runtime.GOOS {
- a.extendProperties(ctx, "target", v.goos, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target."+v.goos, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem(), callback)
t := arch.ArchType
- a.extendProperties(ctx, "target", v.goos+"_"+t.Name, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field+"_"+t.Name).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target."+v.goos+"_"+t.Name, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field+"_"+t.Name).Elem().Elem(), callback)
}
}
- a.extendProperties(ctx, "target", "not_windows", generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target.not_windows", generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem(), callback)
}
// Handle 64-bit device properties in the form:
@@ -564,11 +568,11 @@
// debuggerd that need to know when they are a 32-bit process running on a 64-bit device
if hod.Device() {
if true /* && target_is_64_bit */ {
- a.extendProperties(ctx, "target", "android64", generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android64").Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target.android64", generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android64").Elem().Elem(), callback)
} else {
- a.extendProperties(ctx, "target", "android32", generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android32").Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target.android32", generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android32").Elem().Elem(), callback)
}
}
@@ -583,8 +587,8 @@
// },
if hod.Device() {
t := arch.ArchType
- a.extendProperties(ctx, "target", "android_"+t.Name, generalPropsValue,
- reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android_"+t.Name).Elem().Elem())
+ extendProperties(ctx, "arch_variant", "target.android_"+t.Name, generalPropsValue,
+ reflect.ValueOf(a.archProperties[i].Target).FieldByName("Android_"+t.Name).Elem().Elem(), callback)
}
if ctx.Failed() {
@@ -607,93 +611,3 @@
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()))
- }
- }
-}
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")
+ }
+ }
+}
diff --git a/common/module.go b/common/module.go
index e8c4a87..feaba83 100644
--- a/common/module.go
+++ b/common/module.go
@@ -125,7 +125,7 @@
base.module = m
base.extendedProperties = make(map[string]struct{})
- propertyStructs = append(propertyStructs, &base.commonProperties)
+ propertyStructs = append(propertyStructs, &base.commonProperties, &base.variableProperties)
return m, propertyStructs
}
@@ -194,6 +194,7 @@
module AndroidModule
commonProperties commonProperties
+ variableProperties variableProperties
hostAndDeviceProperties hostAndDeviceProperties
generalProperties []interface{}
archProperties []*archProperties
diff --git a/common/variable.go b/common/variable.go
new file mode 100644
index 0000000..19ec625
--- /dev/null
+++ b/common/variable.go
@@ -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)))
+ }
+}