Merge changes from topics "fix_selects_appending", "refactor_selects" into main

* changes:
  Refactor selects
  Fix bugs when appending selects
  Allow extending configurable propeties with non-configurable properties
This commit is contained in:
Treehugger Robot 2024-04-30 01:15:42 +00:00 committed by Gerrit Code Review
commit ca5ffdd3ce
7 changed files with 739 additions and 348 deletions

View file

@ -67,7 +67,7 @@ func copyProperties(dstValue, srcValue reflect.Value) {
dstFieldValue.Set(srcFieldValue) dstFieldValue.Set(srcFieldValue)
case reflect.Struct: case reflect.Struct:
if isConfigurable(srcFieldValue.Type()) { if isConfigurable(srcFieldValue.Type()) {
dstFieldValue.Set(srcFieldValue.Interface().(configurableReflection).cloneToReflectValuePtr().Elem()) dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Interface().(configurableReflection).clone()))
} else { } else {
copyProperties(dstFieldValue, srcFieldValue) copyProperties(dstFieldValue, srcFieldValue)
} }

View file

@ -36,18 +36,44 @@ type configurableMarker bool
var configurableMarkerType reflect.Type = reflect.TypeOf((*configurableMarker)(nil)).Elem() var configurableMarkerType reflect.Type = reflect.TypeOf((*configurableMarker)(nil)).Elem()
// ConfigurableCondition represents a condition that is being selected on, like
// arch(), os(), soong_config_variable("namespace", "variable"), or other variables.
// It's represented generically as a function name + arguments in blueprint, soong
// interprets the function name and args into specific variable values.
//
// ConfigurableCondition is treated as an immutable object so that it may be shared
// between different configurable properties.
type ConfigurableCondition struct { type ConfigurableCondition struct {
FunctionName string functionName string
Args []string args []string
}
func NewConfigurableCondition(functionName string, args []string) ConfigurableCondition {
return ConfigurableCondition{
functionName: functionName,
args: slices.Clone(args),
}
}
func (c ConfigurableCondition) FunctionName() string {
return c.functionName
}
func (c ConfigurableCondition) NumArgs() int {
return len(c.args)
}
func (c ConfigurableCondition) Arg(i int) string {
return c.args[i]
} }
func (c *ConfigurableCondition) String() string { func (c *ConfigurableCondition) String() string {
var sb strings.Builder var sb strings.Builder
sb.WriteString(c.FunctionName) sb.WriteString(c.functionName)
sb.WriteRune('(') sb.WriteRune('(')
for i, arg := range c.Args { for i, arg := range c.args {
sb.WriteString(strconv.Quote(arg)) sb.WriteString(strconv.Quote(arg))
if i < len(c.Args)-1 { if i < len(c.args)-1 {
sb.WriteString(", ") sb.WriteString(", ")
} }
} }
@ -153,6 +179,16 @@ func (v *configurablePatternType) String() string {
} }
} }
// ConfigurablePattern represents a concrete value for a ConfigurableCase.
// Currently this just means the value of whatever variable is being looked
// up with the ConfigurableCase, but in the future it may be expanded to
// match multiple values (e.g. ranges of integers like 3..7).
//
// ConfigurablePattern can represent different types of values, like
// strings vs bools.
//
// ConfigurablePattern must be immutable so it can be shared between
// different configurable properties.
type ConfigurablePattern struct { type ConfigurablePattern struct {
typ configurablePatternType typ configurablePatternType
stringValue string stringValue string
@ -209,18 +245,17 @@ func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
return p.typ == v.typ.patternType() return p.typ == v.typ.patternType()
} }
// ConfigurableCase represents a set of ConfigurablePatterns
// (exactly 1 pattern per ConfigurableCase), and a value to use
// if all of the patterns are matched.
//
// ConfigurableCase must be immutable so it can be shared between
// different configurable properties.
type ConfigurableCase[T ConfigurableElements] struct { type ConfigurableCase[T ConfigurableElements] struct {
patterns []ConfigurablePattern patterns []ConfigurablePattern
value *T value *T
} }
func (c *ConfigurableCase[T]) Clone() ConfigurableCase[T] {
return ConfigurableCase[T]{
patterns: slices.Clone(c.patterns),
value: copyConfiguredValue(c.value),
}
}
type configurableCaseReflection interface { type configurableCaseReflection interface {
initialize(patterns []ConfigurablePattern, value interface{}) initialize(patterns []ConfigurablePattern, value interface{})
} }
@ -228,9 +263,11 @@ type configurableCaseReflection interface {
var _ configurableCaseReflection = &ConfigurableCase[string]{} var _ configurableCaseReflection = &ConfigurableCase[string]{}
func NewConfigurableCase[T ConfigurableElements](patterns []ConfigurablePattern, value *T) ConfigurableCase[T] { func NewConfigurableCase[T ConfigurableElements](patterns []ConfigurablePattern, value *T) ConfigurableCase[T] {
// Clone the values so they can't be modified from soong
patterns = slices.Clone(patterns)
return ConfigurableCase[T]{ return ConfigurableCase[T]{
patterns: patterns, patterns: patterns,
value: value, value: copyConfiguredValue(value),
} }
} }
@ -257,6 +294,24 @@ func configurableCaseType(configuredType reflect.Type) reflect.Type {
panic("unimplemented") panic("unimplemented")
} }
// for the given T, return the reflect.type of Configurable[T]
func configurableType(configuredType reflect.Type) (reflect.Type, error) {
// I don't think it's possible to do this generically with go's
// current reflection apis unfortunately
switch configuredType.Kind() {
case reflect.String:
return reflect.TypeOf(Configurable[string]{}), nil
case reflect.Bool:
return reflect.TypeOf(Configurable[bool]{}), nil
case reflect.Slice:
switch configuredType.Elem().Kind() {
case reflect.String:
return reflect.TypeOf(Configurable[[]string]{}), nil
}
}
return nil, fmt.Errorf("configurable structs can only contain strings, bools, or string slices, found %s", configuredType.String())
}
// Configurable can wrap the type of a blueprint property, // Configurable can wrap the type of a blueprint property,
// in order to allow select statements to be used in bp files // in order to allow select statements to be used in bp files
// for that property. For example, for the property struct: // for that property. For example, for the property struct:
@ -284,11 +339,22 @@ func configurableCaseType(configuredType reflect.Type) reflect.Type {
// All configurable properties support being unset, so there is // All configurable properties support being unset, so there is
// no need to use a pointer type like Configurable[*string]. // no need to use a pointer type like Configurable[*string].
type Configurable[T ConfigurableElements] struct { type Configurable[T ConfigurableElements] struct {
marker configurableMarker marker configurableMarker
propertyName string propertyName string
conditions []ConfigurableCondition inner *configurableInner[T]
cases []ConfigurableCase[T] }
appendWrapper *appendWrapper[T]
type configurableInner[T ConfigurableElements] struct {
single singleConfigurable[T]
replace bool
next *configurableInner[T]
}
// singleConfigurable must be immutable so it can be reused
// between multiple configurables
type singleConfigurable[T ConfigurableElements] struct {
conditions []ConfigurableCondition
cases []ConfigurableCase[T]
} }
// Ignore the warning about the unused marker variable, it's used via reflection // Ignore the warning about the unused marker variable, it's used via reflection
@ -300,52 +366,61 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition,
panic(fmt.Sprintf("All configurables cases must have as many patterns as the configurable has conditions. Expected: %d, found: %d", len(conditions), len(c.patterns))) panic(fmt.Sprintf("All configurables cases must have as many patterns as the configurable has conditions. Expected: %d, found: %d", len(conditions), len(c.patterns)))
} }
} }
// Clone the slices so they can't be modified from soong
conditions = slices.Clone(conditions)
cases = slices.Clone(cases)
return Configurable[T]{ return Configurable[T]{
conditions: conditions, inner: &configurableInner[T]{
cases: cases, single: singleConfigurable[T]{
appendWrapper: &appendWrapper[T]{}, conditions: conditions,
cases: cases,
},
},
} }
} }
// appendWrapper exists so that we can set the value of append
// from a non-pointer method receiver. (setAppend)
type appendWrapper[T ConfigurableElements] struct {
append Configurable[T]
replace bool
}
// Get returns the final value for the configurable property. // Get returns the final value for the configurable property.
// A configurable property may be unset, in which case Get will return nil. // A configurable property may be unset, in which case Get will return nil.
func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) *T { func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) *T {
if c == nil || c.appendWrapper == nil { result := c.inner.evaluate(c.propertyName, evaluator)
return nil // Copy the result so that it can't be changed from soong
} return copyConfiguredValue(result)
if c.appendWrapper.replace {
return replaceConfiguredValues(
c.evaluateNonTransitive(evaluator),
c.appendWrapper.append.Get(evaluator),
)
} else {
return appendConfiguredValues(
c.evaluateNonTransitive(evaluator),
c.appendWrapper.append.Get(evaluator),
)
}
} }
// GetOrDefault is the same as Get, but will return the provided default value if the property was unset. // GetOrDefault is the same as Get, but will return the provided default value if the property was unset.
func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultValue T) T { func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultValue T) T {
result := c.Get(evaluator) result := c.inner.evaluate(c.propertyName, evaluator)
if result != nil { if result != nil {
return *result // Copy the result so that it can't be changed from soong
return copyAndDereferenceConfiguredValue(result)
} }
return defaultValue return defaultValue
} }
func (c *Configurable[T]) evaluateNonTransitive(evaluator ConfigurableEvaluator) *T { func (c *configurableInner[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T {
if c == nil {
return nil
}
if c.next == nil {
return c.single.evaluateNonTransitive(propertyName, evaluator)
}
if c.replace {
return replaceConfiguredValues(
c.single.evaluateNonTransitive(propertyName, evaluator),
c.next.evaluate(propertyName, evaluator),
)
} else {
return appendConfiguredValues(
c.single.evaluateNonTransitive(propertyName, evaluator),
c.next.evaluate(propertyName, evaluator),
)
}
}
func (c *singleConfigurable[T]) evaluateNonTransitive(propertyName string, evaluator ConfigurableEvaluator) *T {
for i, case_ := range c.cases { for i, case_ := range c.cases {
if len(c.conditions) != len(case_.patterns) { if len(c.conditions) != len(case_.patterns) {
evaluator.PropertyErrorf(c.propertyName, "Expected each case to have as many patterns as conditions. conditions: %d, len(cases[%d].patterns): %d", len(c.conditions), i, len(case_.patterns)) evaluator.PropertyErrorf(propertyName, "Expected each case to have as many patterns as conditions. conditions: %d, len(cases[%d].patterns): %d", len(c.conditions), i, len(case_.patterns))
return nil return nil
} }
} }
@ -355,13 +430,13 @@ func (c *Configurable[T]) evaluateNonTransitive(evaluator ConfigurableEvaluator)
} else if len(c.cases) == 1 { } else if len(c.cases) == 1 {
return c.cases[0].value return c.cases[0].value
} else { } else {
evaluator.PropertyErrorf(c.propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases)) evaluator.PropertyErrorf(propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases))
return nil return nil
} }
} }
values := make([]ConfigurableValue, len(c.conditions)) values := make([]ConfigurableValue, len(c.conditions))
for i, condition := range c.conditions { for i, condition := range c.conditions {
values[i] = evaluator.EvaluateConfiguration(condition, c.propertyName) values[i] = evaluator.EvaluateConfiguration(condition, propertyName)
} }
foundMatch := false foundMatch := false
var result *T var result *T
@ -369,7 +444,7 @@ func (c *Configurable[T]) evaluateNonTransitive(evaluator ConfigurableEvaluator)
allMatch := true allMatch := true
for i, pat := range case_.patterns { for i, pat := range case_.patterns {
if !pat.matchesValueType(values[i]) { if !pat.matchesValueType(values[i]) {
evaluator.PropertyErrorf(c.propertyName, "Expected all branches of a select on condition %s to have type %s, found %s", c.conditions[i].String(), values[i].typ.String(), pat.typ.String()) evaluator.PropertyErrorf(propertyName, "Expected all branches of a select on condition %s to have type %s, found %s", c.conditions[i].String(), values[i].typ.String(), pat.typ.String())
return nil return nil
} }
if !pat.matchesValue(values[i]) { if !pat.matchesValue(values[i]) {
@ -385,7 +460,7 @@ func (c *Configurable[T]) evaluateNonTransitive(evaluator ConfigurableEvaluator)
if foundMatch { if foundMatch {
return result return result
} }
evaluator.PropertyErrorf(c.propertyName, "%s had value %s, which was not handled by the select statement", c.conditions, values) evaluator.PropertyErrorf(propertyName, "%s had value %s, which was not handled by the select statement", c.conditions, values)
return nil return nil
} }
@ -447,9 +522,9 @@ func replaceConfiguredValues[T ConfigurableElements](a, b *T) *T {
// the property unpacking code. You can't call unexported methods from reflection, // the property unpacking code. You can't call unexported methods from reflection,
// (at least without unsafe pointer trickery) so this is the next best thing. // (at least without unsafe pointer trickery) so this is the next best thing.
type configurableReflection interface { type configurableReflection interface {
setAppend(append any, replace bool) setAppend(append any, replace bool, prepend bool)
configuredType() reflect.Type configuredType() reflect.Type
cloneToReflectValuePtr() reflect.Value clone() any
isEmpty() bool isEmpty() bool
} }
@ -464,64 +539,135 @@ var _ configurablePtrReflection = &Configurable[string]{}
func (c *Configurable[T]) initialize(propertyName string, conditions []ConfigurableCondition, cases any) { func (c *Configurable[T]) initialize(propertyName string, conditions []ConfigurableCondition, cases any) {
c.propertyName = propertyName c.propertyName = propertyName
c.conditions = conditions c.inner = &configurableInner[T]{
c.cases = cases.([]ConfigurableCase[T]) single: singleConfigurable[T]{
c.appendWrapper = &appendWrapper[T]{} conditions: conditions,
cases: cases.([]ConfigurableCase[T]),
},
}
} }
func (c Configurable[T]) setAppend(append any, replace bool) { func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) {
if c.appendWrapper.append.isEmpty() { a := append.(Configurable[T])
c.appendWrapper.append = append.(Configurable[T]) if a.inner.isEmpty() {
c.appendWrapper.replace = replace return
} else {
c.appendWrapper.append.setAppend(append, replace)
} }
c.inner.setAppend(a.inner, replace, prepend)
if c.inner == c.inner.next {
panic("pointer loop")
}
}
func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace bool, prepend bool) {
if c.isEmpty() {
*c = *append.clone()
} else if prepend {
if replace && c.alwaysHasValue() {
// The current value would always override the prepended value, so don't do anything
return
}
// We're going to replace the head node with the one from append, so allocate
// a new one here.
old := &configurableInner[T]{
single: c.single,
replace: c.replace,
next: c.next,
}
*c = *append.clone()
curr := c
for curr.next != nil {
curr = curr.next
}
curr.next = old
curr.replace = replace
} else {
// If we're replacing with something that always has a value set,
// we can optimize the code by replacing our entire append chain here.
if replace && append.alwaysHasValue() {
*c = *append.clone()
} else {
curr := c
for curr.next != nil {
curr = curr.next
}
curr.next = append.clone()
curr.replace = replace
}
}
}
func (c Configurable[T]) clone() any {
return Configurable[T]{
propertyName: c.propertyName,
inner: c.inner.clone(),
}
}
func (c *configurableInner[T]) clone() *configurableInner[T] {
if c == nil {
return nil
}
return &configurableInner[T]{
// We don't need to clone the singleConfigurable because
// it's supposed to be immutable
single: c.single,
replace: c.replace,
next: c.next.clone(),
}
}
func (c *configurableInner[T]) isEmpty() bool {
if c == nil {
return true
}
if !c.single.isEmpty() {
return false
}
return c.next.isEmpty()
} }
func (c Configurable[T]) isEmpty() bool { func (c Configurable[T]) isEmpty() bool {
if c.appendWrapper != nil && !c.appendWrapper.append.isEmpty() { return c.inner.isEmpty()
}
func (c *singleConfigurable[T]) isEmpty() bool {
if c == nil {
return true
}
if len(c.cases) > 1 {
return false return false
} }
return len(c.cases) == 0 if len(c.cases) == 1 && c.cases[0].value != nil {
return false
}
return true
}
func (c *configurableInner[T]) alwaysHasValue() bool {
for curr := c; curr != nil; curr = curr.next {
if curr.single.alwaysHasValue() {
return true
}
}
return false
}
func (c *singleConfigurable[T]) alwaysHasValue() bool {
if len(c.cases) == 0 {
return false
}
for _, c := range c.cases {
if c.value == nil {
return false
}
}
return true
} }
func (c Configurable[T]) configuredType() reflect.Type { func (c Configurable[T]) configuredType() reflect.Type {
return reflect.TypeOf((*T)(nil)).Elem() return reflect.TypeOf((*T)(nil)).Elem()
} }
func (c Configurable[T]) cloneToReflectValuePtr() reflect.Value {
return reflect.ValueOf(c.clone())
}
func (c *Configurable[T]) clone() *Configurable[T] {
if c == nil {
return nil
}
var inner *appendWrapper[T]
if c.appendWrapper != nil {
inner = &appendWrapper[T]{}
if !c.appendWrapper.append.isEmpty() {
inner.append = *c.appendWrapper.append.clone()
inner.replace = c.appendWrapper.replace
}
}
conditionsCopy := make([]ConfigurableCondition, len(c.conditions))
copy(conditionsCopy, c.conditions)
casesCopy := make([]ConfigurableCase[T], len(c.cases))
for i, case_ := range c.cases {
casesCopy[i] = case_.Clone()
}
return &Configurable[T]{
propertyName: c.propertyName,
conditions: conditionsCopy,
cases: casesCopy,
appendWrapper: inner,
}
}
func copyConfiguredValue[T ConfigurableElements](t *T) *T { func copyConfiguredValue[T ConfigurableElements](t *T) *T {
if t == nil { if t == nil {
return nil return nil
@ -531,6 +677,16 @@ func copyConfiguredValue[T ConfigurableElements](t *T) *T {
result := any(slices.Clone(t2)).(T) result := any(slices.Clone(t2)).(T)
return &result return &result
default: default:
return t x := *t
return &x
}
}
func copyAndDereferenceConfiguredValue[T ConfigurableElements](t *T) T {
switch t2 := any(*t).(type) {
case []string:
return any(slices.Clone(t2)).(T)
default:
return *t
} }
} }

View file

@ -384,12 +384,16 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
continue continue
} }
case reflect.Bool, reflect.String, reflect.Slice, reflect.Map: case reflect.Bool, reflect.String, reflect.Slice, reflect.Map:
if srcFieldValue.Type() != dstFieldValue.Type() { // If the types don't match or srcFieldValue cannot be converted to a Configurable type, it's an error
ct, err := configurableType(srcFieldValue.Type())
if srcFieldValue.Type() != dstFieldValue.Type() && (err != nil || dstFieldValue.Type() != ct) {
return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s",
dstFieldValue.Type(), srcFieldValue.Type()) dstFieldValue.Type(), srcFieldValue.Type())
} }
case reflect.Ptr: case reflect.Ptr:
if srcFieldValue.Type() != dstFieldValue.Type() { // If the types don't match or srcFieldValue cannot be converted to a Configurable type, it's an error
ct, err := configurableType(srcFieldValue.Type().Elem())
if srcFieldValue.Type() != dstFieldValue.Type() && (err != nil || dstFieldValue.Type() != ct) {
return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s", return extendPropertyErrorf(propertyName(srcField), "mismatched types %s and %s",
dstFieldValue.Type(), srcFieldValue.Type()) dstFieldValue.Type(), srcFieldValue.Type())
} }
@ -457,25 +461,61 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) { func ExtendBasicType(dstFieldValue, srcFieldValue reflect.Value, order Order) {
prepend := order == Prepend || order == Prepend_replace prepend := order == Prepend || order == Prepend_replace
if !srcFieldValue.IsValid() {
return
}
// If dst is a Configurable and src isn't, promote src to a Configurable.
// This isn't necessary if all property structs are using Configurable values,
// but it's helpful to avoid having to change as many places in the code when
// converting properties to Configurable properties. For example, load hooks
// make their own mini-property structs and append them onto the main property
// structs when they want to change the default values of properties.
srcFieldType := srcFieldValue.Type()
if isConfigurable(dstFieldValue.Type()) && !isConfigurable(srcFieldType) {
var value reflect.Value
if srcFieldType.Kind() == reflect.Pointer {
srcFieldType = srcFieldType.Elem()
if srcFieldValue.IsNil() {
value = srcFieldValue
} else {
// Copy the pointer
value = reflect.New(srcFieldType)
value.Elem().Set(srcFieldValue.Elem())
}
} else {
value = reflect.New(srcFieldType)
value.Elem().Set(srcFieldValue)
}
caseType := configurableCaseType(srcFieldType)
case_ := reflect.New(caseType)
case_.Interface().(configurableCaseReflection).initialize(nil, value.Interface())
cases := reflect.MakeSlice(reflect.SliceOf(caseType), 0, 1)
cases = reflect.Append(cases, case_.Elem())
ct, err := configurableType(srcFieldType)
if err != nil {
// Should be unreachable due to earlier checks
panic(err.Error())
}
temp := reflect.New(ct)
temp.Interface().(configurablePtrReflection).initialize("", nil, cases.Interface())
srcFieldValue = temp.Elem()
}
switch srcFieldValue.Kind() { switch srcFieldValue.Kind() {
case reflect.Struct: case reflect.Struct:
if !isConfigurable(srcFieldValue.Type()) { if !isConfigurable(srcFieldValue.Type()) {
panic("Should be unreachable") panic("Should be unreachable")
} }
if dstFieldValue.Interface().(configurableReflection).isEmpty() { replace := order == Prepend_replace || order == Replace
dstFieldValue.Set(srcFieldValue) unpackedDst := dstFieldValue.Interface().(configurableReflection)
} else if order == Prepend { if unpackedDst.isEmpty() {
srcFieldValue.Interface().(configurableReflection).setAppend(dstFieldValue.Interface(), false) // Properties that were never initialized via unpacking from a bp file value
dstFieldValue.Set(srcFieldValue) // will have a nil inner value, making them unable to be modified without a pointer
} else if order == Append { // like we don't have here. So instead replace the whole configurable object.
dstFieldValue.Interface().(configurableReflection).setAppend(srcFieldValue.Interface(), false) dstFieldValue.Set(reflect.ValueOf(srcFieldValue.Interface().(configurableReflection).clone()))
} else if order == Replace {
dstFieldValue.Interface().(configurableReflection).setAppend(srcFieldValue.Interface(), true)
} else if order == Prepend_replace {
srcFieldValue.Interface().(configurableReflection).setAppend(dstFieldValue.Interface(), true)
dstFieldValue.Set(srcFieldValue)
} else { } else {
panic(fmt.Sprintf("Unexpected order: %d", order)) unpackedDst.setAppend(srcFieldValue.Interface(), replace, prepend)
} }
case reflect.Bool: case reflect.Bool:
// Boolean OR // Boolean OR

View file

@ -1258,138 +1258,11 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
name: "Append configurable", name: "Append configurable",
dst: &struct{ S Configurable[[]string] }{ dst: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{ S: Configurable[[]string]{
conditions: []ConfigurableCondition{{ inner: &configurableInner[[]string]{
FunctionName: "soong_config_variable", single: singleConfigurable[[]string]{
Args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
appendWrapper: &appendWrapper[[]string]{},
},
},
src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "release_variable",
Args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
appendWrapper: &appendWrapper[[]string]{},
},
},
out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "soong_config_variable",
Args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
appendWrapper: &appendWrapper[[]string]{
append: Configurable[[]string]{
conditions: []ConfigurableCondition{{ conditions: []ConfigurableCondition{{
FunctionName: "release_variable", functionName: "soong_config_variable",
Args: []string{ args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
appendWrapper: &appendWrapper[[]string]{},
},
},
},
},
},
{
name: "Prepend configurable",
order: Prepend,
dst: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "soong_config_variable",
Args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
appendWrapper: &appendWrapper[[]string]{},
},
},
src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "release_variable",
Args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
appendWrapper: &appendWrapper[[]string]{},
},
},
out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "release_variable",
Args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
appendWrapper: &appendWrapper[[]string]{
append: Configurable[[]string]{
conditions: []ConfigurableCondition{{
FunctionName: "soong_config_variable",
Args: []string{
"my_namespace", "my_namespace",
"foo", "foo",
}, },
@ -1401,7 +1274,152 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
}}, }},
value: &[]string{"1", "2"}, value: &[]string{"1", "2"},
}}, }},
appendWrapper: &appendWrapper[[]string]{}, },
},
},
},
src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
},
},
},
out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
},
next: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
},
},
},
},
},
{
name: "Prepend configurable",
order: Prepend,
dst: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
},
},
},
},
src: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
},
},
},
out: &struct{ S Configurable[[]string] }{
S: Configurable[[]string]{
inner: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "release_variable",
args: []string{
"bar",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: &[]string{"3", "4"},
}},
},
next: &configurableInner[[]string]{
single: singleConfigurable[[]string]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[[]string]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: &[]string{"1", "2"},
}},
},
}, },
}, },
}, },
@ -1869,6 +1887,150 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
}, },
err: extendPropertyErrorf("s", "mismatched types []int and []string"), err: extendPropertyErrorf("s", "mismatched types []int and []string"),
}, },
{
name: "Append *bool to Configurable[bool]",
order: Append,
dst: []interface{}{
&struct{ S Configurable[bool] }{
S: Configurable[bool]{
inner: &configurableInner[bool]{
single: singleConfigurable[bool]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
},
},
},
},
src: &struct{ S *bool }{
S: BoolPtr(true),
},
out: []interface{}{
&struct{ S Configurable[bool] }{
S: Configurable[bool]{
inner: &configurableInner[bool]{
single: singleConfigurable[bool]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
next: &configurableInner[bool]{
single: singleConfigurable[bool]{
cases: []ConfigurableCase[bool]{{
value: BoolPtr(true),
}},
},
},
},
},
},
},
},
{
name: "Append bool to Configurable[bool]",
order: Append,
dst: []interface{}{
&struct{ S Configurable[bool] }{
S: Configurable[bool]{
inner: &configurableInner[bool]{
single: singleConfigurable[bool]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
},
},
},
},
src: &struct{ S bool }{
S: true,
},
out: []interface{}{
&struct{ S Configurable[bool] }{
S: Configurable[bool]{
inner: &configurableInner[bool]{
single: singleConfigurable[bool]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"foo",
},
}},
cases: []ConfigurableCase[bool]{{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: BoolPtr(true),
}, {
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: BoolPtr(false),
}},
},
next: &configurableInner[bool]{
single: singleConfigurable[bool]{
cases: []ConfigurableCase[bool]{{
value: BoolPtr(true),
}},
},
},
},
},
},
},
},
} }
} }

View file

@ -62,6 +62,10 @@ func typeEqual(v1, v2 reflect.Value) bool {
return true return true
} }
if isConfigurable(v1.Type()) {
return true
}
for i := 0; i < v1.NumField(); i++ { for i := 0; i < v1.NumField(); i++ {
v1 := v1.Field(i) v1 := v1.Field(i)
v2 := v2.Field(i) v2 := v2.Field(i)
@ -94,6 +98,10 @@ func concreteType(v reflect.Value) bool {
return true return true
} }
if isConfigurable(v.Type()) {
return true
}
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
v := v.Field(i) v := v.Field(i)

View file

@ -356,10 +356,13 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
} }
result := Configurable[string]{ result := Configurable[string]{
propertyName: property.Name, propertyName: property.Name,
cases: []ConfigurableCase[string]{{ inner: &configurableInner[string]{
value: &v.Value, single: singleConfigurable[string]{
}}, cases: []ConfigurableCase[string]{{
appendWrapper: &appendWrapper[string]{}, value: &v.Value,
}},
},
},
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
case *parser.Bool: case *parser.Bool:
@ -373,10 +376,13 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
} }
result := Configurable[bool]{ result := Configurable[bool]{
propertyName: property.Name, propertyName: property.Name,
cases: []ConfigurableCase[bool]{{ inner: &configurableInner[bool]{
value: &v.Value, single: singleConfigurable[bool]{
}}, cases: []ConfigurableCase[bool]{{
appendWrapper: &appendWrapper[bool]{}, value: &v.Value,
}},
},
},
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
case *parser.List: case *parser.List:
@ -407,10 +413,13 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
} }
result := Configurable[[]string]{ result := Configurable[[]string]{
propertyName: property.Name, propertyName: property.Name,
cases: []ConfigurableCase[[]string]{{ inner: &configurableInner[[]string]{
value: &value, single: singleConfigurable[[]string]{
}}, cases: []ConfigurableCase[[]string]{{
appendWrapper: &appendWrapper[[]string]{}, value: &value,
}},
},
},
} }
return reflect.ValueOf(&result), true return reflect.ValueOf(&result), true
default: default:
@ -432,8 +441,8 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
args[j] = arg.Value args[j] = arg.Value
} }
conditions[i] = ConfigurableCondition{ conditions[i] = ConfigurableCondition{
FunctionName: cond.FunctionName, functionName: cond.FunctionName,
Args: args, args: args,
} }
} }
@ -512,7 +521,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa
if !ok { if !ok {
return reflect.New(configurableType), false return reflect.New(configurableType), false
} }
result.Interface().(configurableReflection).setAppend(val.Elem().Interface(), false) result.Interface().(configurableReflection).setAppend(val.Elem().Interface(), false, false)
} }
return resultPtr, true return resultPtr, true
default: default:

View file

@ -734,10 +734,13 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[string]{ Foo: Configurable[string]{
propertyName: "foo", propertyName: "foo",
cases: []ConfigurableCase[string]{{ inner: &configurableInner[string]{
value: StringPtr("bar"), single: singleConfigurable[string]{
}}, cases: []ConfigurableCase[string]{{
appendWrapper: &appendWrapper[string]{}, value: StringPtr("bar"),
}},
},
},
}, },
}, },
}, },
@ -755,10 +758,13 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[bool]{ Foo: Configurable[bool]{
propertyName: "foo", propertyName: "foo",
cases: []ConfigurableCase[bool]{{ inner: &configurableInner[bool]{
value: BoolPtr(true), single: singleConfigurable[bool]{
}}, cases: []ConfigurableCase[bool]{{
appendWrapper: &appendWrapper[bool]{}, value: BoolPtr(true),
}},
},
},
}, },
}, },
}, },
@ -776,10 +782,13 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[[]string]{ Foo: Configurable[[]string]{
propertyName: "foo", propertyName: "foo",
cases: []ConfigurableCase[[]string]{{ inner: &configurableInner[[]string]{
value: &[]string{"a", "b"}, single: singleConfigurable[[]string]{
}}, cases: []ConfigurableCase[[]string]{{
appendWrapper: &appendWrapper[[]string]{}, value: &[]string{"a", "b"},
}},
},
},
}, },
}, },
}, },
@ -801,36 +810,39 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[string]{ Foo: Configurable[string]{
propertyName: "foo", propertyName: "foo",
conditions: []ConfigurableCondition{{ inner: &configurableInner[string]{
FunctionName: "soong_config_variable", single: singleConfigurable[string]{
Args: []string{ conditions: []ConfigurableCondition{{
"my_namespace", functionName: "soong_config_variable",
"my_variable", args: []string{
}, "my_namespace",
}}, "my_variable",
cases: []ConfigurableCase[string]{ },
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}}, }},
value: StringPtr("a2"), cases: []ConfigurableCase[string]{
}, {
{ patterns: []ConfigurablePattern{{
patterns: []ConfigurablePattern{{ typ: configurablePatternTypeString,
typ: configurablePatternTypeString, stringValue: "a",
stringValue: "b", }},
}}, value: StringPtr("a2"),
value: StringPtr("b2"), },
}, {
{ patterns: []ConfigurablePattern{{
patterns: []ConfigurablePattern{{ typ: configurablePatternTypeString,
typ: configurablePatternTypeDefault, stringValue: "b",
}}, }},
value: StringPtr("c2"), value: StringPtr("b2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: StringPtr("c2"),
},
},
}, },
}, },
appendWrapper: &appendWrapper[string]{},
}, },
}, },
}, },
@ -856,68 +868,70 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[string]{ Foo: Configurable[string]{
propertyName: "foo", propertyName: "foo",
conditions: []ConfigurableCondition{{ inner: &configurableInner[string]{
FunctionName: "soong_config_variable", single: singleConfigurable[string]{
Args: []string{
"my_namespace",
"my_variable",
},
}},
cases: []ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "a",
}},
value: StringPtr("a2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "b",
}},
value: StringPtr("b2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: StringPtr("c2"),
},
},
appendWrapper: &appendWrapper[string]{
append: Configurable[string]{
propertyName: "foo",
conditions: []ConfigurableCondition{{ conditions: []ConfigurableCondition{{
FunctionName: "soong_config_variable", functionName: "soong_config_variable",
Args: []string{ args: []string{
"my_namespace", "my_namespace",
"my_2nd_variable", "my_variable",
}, },
}}, }},
cases: []ConfigurableCase[string]{ cases: []ConfigurableCase[string]{
{ {
patterns: []ConfigurablePattern{{ patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString, typ: configurablePatternTypeString,
stringValue: "d", stringValue: "a",
}}, }},
value: StringPtr("d2"), value: StringPtr("a2"),
}, },
{ {
patterns: []ConfigurablePattern{{ patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString, typ: configurablePatternTypeString,
stringValue: "e", stringValue: "b",
}}, }},
value: StringPtr("e2"), value: StringPtr("b2"),
}, },
{ {
patterns: []ConfigurablePattern{{ patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault, typ: configurablePatternTypeDefault,
}}, }},
value: StringPtr("f2"), value: StringPtr("c2"),
},
},
},
next: &configurableInner[string]{
single: singleConfigurable[string]{
conditions: []ConfigurableCondition{{
functionName: "soong_config_variable",
args: []string{
"my_namespace",
"my_2nd_variable",
},
}},
cases: []ConfigurableCase[string]{
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "d",
}},
value: StringPtr("d2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeString,
stringValue: "e",
}},
value: StringPtr("e2"),
},
{
patterns: []ConfigurablePattern{{
typ: configurablePatternTypeDefault,
}},
value: StringPtr("f2"),
},
}, },
}, },
appendWrapper: &appendWrapper[string]{},
}, },
}, },
}, },
@ -941,21 +955,23 @@ var validUnpackTestCases = []struct {
}{ }{
Foo: Configurable[string]{ Foo: Configurable[string]{
propertyName: "foo", propertyName: "foo",
cases: []ConfigurableCase[string]{ inner: &configurableInner[string]{
{ single: singleConfigurable[string]{
value: StringPtr("asdf"), cases: []ConfigurableCase[string]{{
value: StringPtr("asdf"),
}},
}, },
}, },
appendWrapper: &appendWrapper[string]{},
}, },
Bar: Configurable[bool]{ Bar: Configurable[bool]{
propertyName: "bar", propertyName: "bar",
cases: []ConfigurableCase[bool]{ inner: &configurableInner[bool]{
{ single: singleConfigurable[bool]{
value: BoolPtr(true), cases: []ConfigurableCase[bool]{{
value: BoolPtr(true),
}},
}, },
}, },
appendWrapper: &appendWrapper[bool]{},
}, },
}, },
}, },