From c472e38ec1ecd7ef7fe384766371a420f77adc88 Mon Sep 17 00:00:00 2001 From: Cole Faust Date: Tue, 27 Aug 2024 14:54:04 -0700 Subject: [PATCH] Add PostProcessors to configurable properties Some module types currently evaluate configurable properties in load hooks, modify the results, and pass them onto properties of other modules. Evaluating configurable properties in load hooks is problematic, it happens so early that we can't decide the configuration beforehand. Add a "post processors" mechanism to configurable properties where the result of evaluating the property will be passed through a post processing function before being returned from Get(). This essentially allows you to modify the property without evaluating it. Bug: 362579941 Test: m nothing --no-skip-soong-tests Change-Id: Ibddb3f14b3433364ba474b964c701e8915d4dc85 --- Android.bp | 1 + proptools/configurable.go | 256 ++++++++++++++++- proptools/configurable_test.go | 77 +++++ proptools/extend_test.go | 501 +++++++++++++++------------------ proptools/unpack.go | 6 + proptools/unpack_test.go | 498 ++++++++++++++++---------------- 6 files changed, 791 insertions(+), 548 deletions(-) create mode 100644 proptools/configurable_test.go diff --git a/Android.bp b/Android.bp index ee0ede0..9fc62e6 100644 --- a/Android.bp +++ b/Android.bp @@ -133,6 +133,7 @@ bootstrap_go_package { ], testSrcs: [ "proptools/clone_test.go", + "proptools/configurable_test.go", "proptools/escape_test.go", "proptools/extend_test.go", "proptools/filter_test.go", diff --git a/proptools/configurable.go b/proptools/configurable.go index 980ee47..4dfe007 100644 --- a/proptools/configurable.go +++ b/proptools/configurable.go @@ -412,6 +412,22 @@ type Configurable[T ConfigurableElements] struct { marker configurableMarker propertyName string inner *configurableInner[T] + // See Configurable.evaluate for a description of the postProcessor algorithm and + // why this is a 2d list + postProcessors *[][]postProcessor[T] +} + +type postProcessor[T ConfigurableElements] struct { + f func(T) T + // start and end represent the range of configurableInners + // that this postprocessor is applied to. When appending two configurables + // together, the start and end values will stay the same for the left + // configurable's postprocessors, but the rights will be rebased by the + // number of configurableInners in the left configurable. This way + // the postProcessors still only apply to the configurableInners they + // origionally applied to before the appending. + start int + end int } type configurableInner[T ConfigurableElements] struct { @@ -440,6 +456,7 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition, // Clone the slices so they can't be modified from soong conditions = slices.Clone(conditions) cases = slices.Clone(cases) + var zeroPostProcessors [][]postProcessor[T] return Configurable[T]{ inner: &configurableInner[T]{ single: singleConfigurable[T]{ @@ -447,35 +464,77 @@ func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition, cases: cases, }, }, + postProcessors: &zeroPostProcessors, } } +func newConfigurableWithPropertyName[T ConfigurableElements](propertyName string, conditions []ConfigurableCondition, cases []ConfigurableCase[T], addScope bool) Configurable[T] { + result := NewConfigurable(conditions, cases) + result.propertyName = propertyName + if addScope { + for curr := result.inner; curr != nil; curr = curr.next { + curr.single.scope = parser.NewScope(nil) + } + } + return result +} + func (c *Configurable[T]) AppendSimpleValue(value T) { value = copyConfiguredValue(value) // This may be a property that was never initialized from a bp file if c.inner == nil { - c.inner = &configurableInner[T]{ - single: singleConfigurable[T]{ - cases: []ConfigurableCase[T]{{ - value: configuredValueToExpression(value), - }}, - }, - } + c.initialize(nil, "", nil, []ConfigurableCase[T]{{ + value: configuredValueToExpression(value), + }}) return } c.inner.appendSimpleValue(value) } +// AddPostProcessor adds a function that will modify the result of +// Get() when Get() is called. It operates on all the current contents +// of the Configurable property, but if other values are appended to +// the Configurable property afterwards, the postProcessor will not run +// on them. This can be useful to essentially modify a configurable +// property without evaluating it. +func (c *Configurable[T]) AddPostProcessor(p func(T) T) { + // Add the new postProcessor on top of the tallest stack of postProcessors. + // See Configurable.evaluate for more details on the postProcessors algorithm + // and data structure. + num_links := c.inner.numLinks() + if c.postProcessors == nil || len(*c.postProcessors) == 0 { + c.postProcessors = &[][]postProcessor[T]{{{ + f: p, + start: 0, + end: num_links, + }}} + } else { + deepestI := 0 + deepestDepth := 0 + for i := 0; i < len(*c.postProcessors); i++ { + if len((*c.postProcessors)[i]) > deepestDepth { + deepestDepth = len((*c.postProcessors)[i]) + deepestI = i + } + } + (*c.postProcessors)[deepestI] = append((*c.postProcessors)[deepestI], postProcessor[T]{ + f: p, + start: 0, + end: num_links, + }) + } +} + // Get returns the final value for the configurable property. // A configurable property may be unset, in which case Get will return nil. func (c *Configurable[T]) Get(evaluator ConfigurableEvaluator) ConfigurableOptional[T] { - result := c.inner.evaluate(c.propertyName, evaluator) + result := c.evaluate(c.propertyName, evaluator) return configuredValuePtrToOptional(result) } // 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 { - result := c.inner.evaluate(c.propertyName, evaluator) + result := c.evaluate(c.propertyName, evaluator) if result != nil { // Copy the result so that it can't be changed from soong return copyConfiguredValue(*result) @@ -483,6 +542,127 @@ func (c *Configurable[T]) GetOrDefault(evaluator ConfigurableEvaluator, defaultV return defaultValue } +type valueAndIndices[T ConfigurableElements] struct { + value *T + replace bool + // Similar to start/end in postProcessor, these represent the origional + // range or configurableInners that this merged group represents. It's needed + // in order to apply recursive postProcessors to only the relevant + // configurableInners, even after those configurableInners have been merged + // in order to apply an earlier postProcessor. + start int + end int +} + +func (c *Configurable[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T { + if c.inner == nil { + return nil + } + + if len(*c.postProcessors) == 0 { + // Use a simpler algorithm if there are no postprocessors + return c.inner.evaluate(propertyName, evaluator) + } + + // The basic idea around evaluating with postprocessors is that each individual + // node in the chain (each configurableInner) is first evaluated, and then when + // a postprocessor operates on a certain range, that range is merged before passing + // it to the postprocessor. We want postProcessors to only accept a final merged + // value instead of a linked list, but at the same time, only operate over a portion + // of the list. If more configurables are appended onto this one, their values won't + // be operated on by the existing postProcessors, but they may have their own + // postprocessors. + // + // _____________________ + // | __________| + // ______ | _____| ___ + // | | | | | | + // a -> b -> c -> d -> e -> f -> g + // + // In this diagram, the letters along the bottom is the chain of configurableInners. + // The brackets on top represent postprocessors, where higher brackets are processed + // after lower ones. + // + // To evaluate this example, first we evaluate the raw values for all nodes a->g. + // Then we merge nodes a/b and d/e and apply the postprocessors to their merged values, + // and also to g. Those merged and postprocessed nodes are then reinserted into the + // list, and we move on to doing the higher level postprocessors (starting with the c->e one) + // in the same way. When all postprocessors are done, a final merge is done on anything + // leftover. + // + // The Configurable.postProcessors field is a 2d array to represent this hierarchy. + // The outer index moves right on this graph, the inner index goes up. + // When adding a new postProcessor, it will always be the last postProcessor to run + // until another is added or another configurable is appended. So in AddPostProcessor(), + // we add it to the tallest existing stack. + + var currentValues []valueAndIndices[T] + for curr, i := c.inner, 0; curr != nil; curr, i = curr.next, i+1 { + value := curr.single.evaluateNonTransitive(propertyName, evaluator) + currentValues = append(currentValues, valueAndIndices[T]{ + value: value, + replace: curr.replace, + start: i, + end: i + 1, + }) + } + + if c.postProcessors == nil || len(*c.postProcessors) == 0 { + return mergeValues(currentValues).value + } + + foundPostProcessor := true + for depth := 0; foundPostProcessor; depth++ { + foundPostProcessor = false + var newValues []valueAndIndices[T] + i := 0 + for _, postProcessorGroup := range *c.postProcessors { + if len(postProcessorGroup) > depth { + foundPostProcessor = true + postProcessor := postProcessorGroup[depth] + startI := 0 + endI := 0 + for currentValues[startI].start < postProcessor.start { + startI++ + } + for currentValues[endI].end < postProcessor.end { + endI++ + } + endI++ + newValues = append(newValues, currentValues[i:startI]...) + merged := mergeValues(currentValues[startI:endI]) + if merged.value != nil { + processed := postProcessor.f(*merged.value) + merged.value = &processed + } + newValues = append(newValues, merged) + i = endI + } + } + newValues = append(newValues, currentValues[i:]...) + currentValues = newValues + } + + return mergeValues(currentValues).value +} + +func mergeValues[T ConfigurableElements](values []valueAndIndices[T]) valueAndIndices[T] { + if len(values) < 0 { + panic("Expected at least 1 value in mergeValues") + } + result := values[0] + for i := 1; i < len(values); i++ { + if result.replace { + result.value = replaceConfiguredValues(result.value, values[i].value) + } else { + result.value = appendConfiguredValues(result.value, values[i].value) + } + result.end = values[i].end + result.replace = values[i].replace + } + return result +} + func (c *configurableInner[T]) evaluate(propertyName string, evaluator ConfigurableEvaluator) *T { if c == nil { return nil @@ -668,6 +848,12 @@ func (c *Configurable[T]) initialize(scope *parser.Scope, propertyName string, c scope: scope, }, } + var postProcessors [][]postProcessor[T] + c.postProcessors = &postProcessors +} + +func (c *Configurable[T]) Append(other Configurable[T]) { + c.setAppend(other, false, false) } func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) { @@ -675,12 +861,37 @@ func (c Configurable[T]) setAppend(append any, replace bool, prepend bool) { if a.inner.isEmpty() { return } + + if prepend { + newBase := a.inner.numLinks() + *c.postProcessors = appendPostprocessors(*a.postProcessors, *c.postProcessors, newBase) + } else { + newBase := c.inner.numLinks() + *c.postProcessors = appendPostprocessors(*c.postProcessors, *a.postProcessors, newBase) + } + c.inner.setAppend(a.inner, replace, prepend) if c.inner == c.inner.next { panic("pointer loop") } } +func appendPostprocessors[T ConfigurableElements](a, b [][]postProcessor[T], newBase int) [][]postProcessor[T] { + var result [][]postProcessor[T] + for i := 0; i < len(a); i++ { + result = append(result, slices.Clone(a[i])) + } + for i := 0; i < len(b); i++ { + n := slices.Clone(b[i]) + for j := 0; j < len(n); j++ { + n[j].start += newBase + n[j].end += newBase + } + result = append(result, n) + } + return result +} + func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace bool, prepend bool) { if c.isEmpty() { *c = *append.clone() @@ -719,6 +930,14 @@ func (c *configurableInner[T]) setAppend(append *configurableInner[T], replace b } } +func (c *configurableInner[T]) numLinks() int { + result := 0 + for curr := c; curr != nil; curr = curr.next { + result++ + } + return result +} + func (c *configurableInner[T]) appendSimpleValue(value T) { if c.next == nil { c.replace = false @@ -761,10 +980,20 @@ func (c *singleConfigurable[T]) printfInto(value string) error { } func (c Configurable[T]) clone() any { - return Configurable[T]{ - propertyName: c.propertyName, - inner: c.inner.clone(), + var newPostProcessors *[][]postProcessor[T] + if c.postProcessors != nil { + x := appendPostprocessors(*c.postProcessors, nil, 0) + newPostProcessors = &x } + return Configurable[T]{ + propertyName: c.propertyName, + inner: c.inner.clone(), + postProcessors: newPostProcessors, + } +} + +func (c Configurable[T]) Clone() Configurable[T] { + return c.clone().(Configurable[T]) } func (c *configurableInner[T]) clone() *configurableInner[T] { @@ -973,6 +1202,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[string]{}, }) case reflect.Bool: return reflect.ValueOf(Configurable[bool]{ @@ -983,6 +1213,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[bool]{}, }) case reflect.Slice: return reflect.ValueOf(Configurable[[]string]{ @@ -993,6 +1224,7 @@ func promoteValueToConfigurable(origional reflect.Value) reflect.Value { }}, }, }, + postProcessors: &[][]postProcessor[[]string]{}, }) default: panic(fmt.Sprintf("Can't convert %s property to a configurable", origional.Kind().String())) diff --git a/proptools/configurable_test.go b/proptools/configurable_test.go new file mode 100644 index 0000000..f3ea417 --- /dev/null +++ b/proptools/configurable_test.go @@ -0,0 +1,77 @@ +package proptools + +import ( + "fmt" + "reflect" + "testing" +) + +func TestPostProcessor(t *testing.T) { + // Same as the ascii art example in Configurable.evaluate() + prop := NewConfigurable[[]string](nil, nil) + prop.AppendSimpleValue([]string{"a"}) + prop.AppendSimpleValue([]string{"b"}) + prop.AddPostProcessor(addToElements("1")) + + prop2 := NewConfigurable[[]string](nil, nil) + prop2.AppendSimpleValue([]string{"c"}) + + prop3 := NewConfigurable[[]string](nil, nil) + prop3.AppendSimpleValue([]string{"d"}) + prop3.AppendSimpleValue([]string{"e"}) + prop3.AddPostProcessor(addToElements("2")) + + prop4 := NewConfigurable[[]string](nil, nil) + prop4.AppendSimpleValue([]string{"f"}) + + prop5 := NewConfigurable[[]string](nil, nil) + prop5.AppendSimpleValue([]string{"g"}) + prop5.AddPostProcessor(addToElements("3")) + + prop2.Append(prop3) + prop2.AddPostProcessor(addToElements("z")) + + prop.Append(prop2) + prop.AddPostProcessor(addToElements("y")) + prop.Append(prop4) + prop.Append(prop5) + + expected := []string{"a1y", "b1y", "czy", "d2zy", "e2zy", "f", "g3"} + x := prop.Get(&configurableEvalutorForTesting{}) + if !reflect.DeepEqual(x.Get(), expected) { + t.Fatalf("Expected %v, got %v", expected, x.Get()) + } +} + +func addToElements(s string) func([]string) []string { + return func(arr []string) []string { + for i := range arr { + arr[i] = arr[i] + s + } + return arr + } +} + +type configurableEvalutorForTesting struct { + vars map[string]string +} + +func (e *configurableEvalutorForTesting) EvaluateConfiguration(condition ConfigurableCondition, property string) ConfigurableValue { + if condition.functionName != "f" { + panic("Expected functionName to be f") + } + if len(condition.args) != 1 { + panic("Expected exactly 1 arg") + } + val, ok := e.vars[condition.args[0]] + if ok { + return ConfigurableValueString(val) + } + return ConfigurableValueUndefined() +} + +func (e *configurableEvalutorForTesting) PropertyErrorf(property, fmtString string, args ...interface{}) { + panic(fmt.Sprintf(fmtString, args...)) +} + +var _ ConfigurableEvaluator = (*configurableEvalutorForTesting)(nil) diff --git a/proptools/extend_test.go b/proptools/extend_test.go index 0148204..4fabf52 100644 --- a/proptools/extend_test.go +++ b/proptools/extend_test.go @@ -1259,196 +1259,166 @@ func appendPropertiesTestCases() []appendPropertyTestCase { { name: "Append configurable", 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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, - }, + S: NewConfigurable[[]string]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, + S: func() Configurable[[]string] { + result := NewConfigurable([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - next: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - conditions: []ConfigurableCondition{{ - functionName: "release_variable", - args: []string{ - "bar", - }, - }}, - cases: []ConfigurableCase[[]string]{{ - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }}, + ) + result.Append(NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }})) + return result + }(), }, }, { 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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "4"}, - }}, - }}, - }, + S: NewConfigurable([]ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "3"}, - &parser.String{Value: "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: &parser.List{Values: []parser.Expression{ - &parser.String{Value: "1"}, - &parser.String{Value: "2"}, - }}, - }}, + S: func() Configurable[[]string] { + result := NewConfigurable( + []ConfigurableCondition{{ + functionName: "release_variable", + args: []string{ + "bar", }, - }, - }, - }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "3"}, + &parser.String{Value: "4"}, + }}, + }}, + ) + result.Append(NewConfigurable( + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", + }, + }}, + []ConfigurableCase[[]string]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.List{Values: []parser.Expression{ + &parser.String{Value: "1"}, + &parser.String{Value: "2"}, + }}, + }})) + return result + }(), }, }, } @@ -1918,31 +1888,24 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { 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: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, + S: NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}), }, }, src: &struct{ S *bool }{ @@ -1950,38 +1913,30 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { }, 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: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, + S: func() Configurable[bool] { + result := NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - next: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{Value: true}, - }}, - }, - }, - }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ) + result.AppendSimpleValue(true) + return result + }(), }, }, }, @@ -1990,31 +1945,26 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { 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: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, + S: NewConfigurable[bool]([]ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ), }, }, src: &struct{ S bool }{ @@ -2022,38 +1972,31 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase { }, 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: &parser.Bool{Value: true}, - }, { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.Bool{Value: false}, - }}, - }, - next: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{Value: true}, - }}, + S: func() Configurable[bool] { + result := NewConfigurable[bool]( + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "foo", }, - }, - }, - }, + }}, + []ConfigurableCase[bool]{{ + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", + }}, + value: &parser.Bool{Value: true}, + }, { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.Bool{Value: false}, + }}, + ) + result.AppendSimpleValue(true) + return result + }(), }, }, }, diff --git a/proptools/unpack.go b/proptools/unpack.go index 91eae79..712e78c 100644 --- a/proptools/unpack.go +++ b/proptools/unpack.go @@ -354,6 +354,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }) return reflect.New(configurableType), false } + var postProcessors [][]postProcessor[string] result := Configurable[string]{ propertyName: property.Name, inner: &configurableInner[string]{ @@ -363,6 +364,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true case *parser.Bool: @@ -374,6 +376,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }) return reflect.New(configurableType), false } + var postProcessors [][]postProcessor[bool] result := Configurable[bool]{ propertyName: property.Name, inner: &configurableInner[bool]{ @@ -383,6 +386,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true case *parser.List: @@ -411,6 +415,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa value[i] = exprUnpacked.Interface().(string) } } + var postProcessors [][]postProcessor[[]string] result := Configurable[[]string]{ propertyName: property.Name, inner: &configurableInner[[]string]{ @@ -420,6 +425,7 @@ func (ctx *unpackContext) unpackToConfigurable(propertyName string, property *pa }}, }, }, + postProcessors: &postProcessors, } return reflect.ValueOf(&result), true default: diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go index 3af79fa..10ac0aa 100644 --- a/proptools/unpack_test.go +++ b/proptools/unpack_test.go @@ -733,23 +733,21 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - cases: []ConfigurableCase[string]{{ - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 17, - Line: 3, - Column: 10, - }, - Value: "bar", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[string]{{ + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + Value: "bar", }, - }, - }, + }}, + false, + ), }, }, }, @@ -764,24 +762,22 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[bool] }{ - Foo: Configurable[bool]{ - propertyName: "foo", - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{ - LiteralPos: scanner.Position{ - Offset: 17, - Line: 3, - Column: 10, - }, - Value: true, - Token: "true", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[bool]{{ + value: &parser.Bool{ + LiteralPos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + Value: true, + Token: "true", }, - }, - }, + }}, + false, + ), }, }, }, @@ -796,45 +792,43 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[[]string] }{ - Foo: Configurable[[]string]{ - propertyName: "foo", - inner: &configurableInner[[]string]{ - single: singleConfigurable[[]string]{ - cases: []ConfigurableCase[[]string]{{ - value: &parser.List{ - LBracePos: scanner.Position{ - Offset: 17, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[[]string]{{ + value: &parser.List{ + LBracePos: scanner.Position{ + Offset: 17, + Line: 3, + Column: 10, + }, + RBracePos: scanner.Position{ + Offset: 26, + Line: 3, + Column: 19, + }, + Values: []parser.Expression{ + &parser.String{ + LiteralPos: scanner.Position{ + Offset: 18, Line: 3, - Column: 10, - }, - RBracePos: scanner.Position{ - Offset: 26, - Line: 3, - Column: 19, - }, - Values: []parser.Expression{ - &parser.String{ - LiteralPos: scanner.Position{ - Offset: 18, - Line: 3, - Column: 11, - }, - Value: "a", - }, - &parser.String{ - LiteralPos: scanner.Position{ - Offset: 23, - Line: 3, - Column: 16, - }, - Value: "b", - }, + Column: 11, }, + Value: "a", }, - }}, + &parser.String{ + LiteralPos: scanner.Position{ + Offset: 23, + Line: 3, + Column: 16, + }, + Value: "b", + }, + }, }, - }, - }, + }}, + false, + ), }, }, }, @@ -853,64 +847,60 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_variable", - }, + Foo: newConfigurableWithPropertyName( + "foo", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_variable", + }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 90, - Line: 4, - Column: 11, - }, - Value: "a2", - }, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 90, + Line: 4, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 106, - Line: 5, - Column: 11, - }, - Value: "b2", - }, + Value: "a2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 106, + Line: 5, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 126, - Line: 6, - Column: 15, - }, - Value: "c2", - }, + Value: "b2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 126, + Line: 6, + Column: 15, }, + Value: "c2", }, }, }, - }, + true, + ), }, }, }, @@ -933,119 +923,117 @@ var validUnpackTestCases = []struct { &struct { Foo Configurable[string] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_variable", - }, - }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "a", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 90, - Line: 4, - Column: 11, - }, - Value: "a2", - }, - }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "b", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 106, - Line: 5, - Column: 11, - }, - Value: "b2", - }, - }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 126, - Line: 6, - Column: 15, - }, - Value: "c2", - }, - }, + Foo: func() Configurable[string] { + result := newConfigurableWithPropertyName( + "foo", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_variable", }, - }, - next: &configurableInner[string]{ - single: singleConfigurable[string]{ - scope: parser.NewScope(nil), - conditions: []ConfigurableCondition{{ - functionName: "soong_config_variable", - args: []string{ - "my_namespace", - "my_2nd_variable", - }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "a", }}, - cases: []ConfigurableCase[string]{ - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "d", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 218, - Line: 8, - Column: 11, - }, - Value: "d2", - }, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 90, + Line: 4, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeString, - stringValue: "e", - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 234, - Line: 9, - Column: 11, - }, - Value: "e2", - }, + Value: "a2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "b", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 106, + Line: 5, + Column: 11, }, - { - patterns: []ConfigurablePattern{{ - typ: configurablePatternTypeDefault, - }}, - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 254, - Line: 10, - Column: 15, - }, - Value: "f2", - }, + Value: "b2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 126, + Line: 6, + Column: 15, }, + Value: "c2", }, }, }, - }, - }, + true, + ) + result.Append(newConfigurableWithPropertyName( + "", + []ConfigurableCondition{{ + functionName: "soong_config_variable", + args: []string{ + "my_namespace", + "my_2nd_variable", + }, + }}, + []ConfigurableCase[string]{ + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "d", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 218, + Line: 8, + Column: 11, + }, + Value: "d2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeString, + stringValue: "e", + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 234, + Line: 9, + Column: 11, + }, + Value: "e2", + }, + }, + { + patterns: []ConfigurablePattern{{ + typ: configurablePatternTypeDefault, + }}, + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 254, + Line: 10, + Column: 15, + }, + Value: "f2", + }, + }, + }, + true, + )) + return result + }(), }, }, }, @@ -1064,41 +1052,37 @@ var validUnpackTestCases = []struct { Foo Configurable[string] Bar Configurable[bool] }{ - Foo: Configurable[string]{ - propertyName: "foo", - inner: &configurableInner[string]{ - single: singleConfigurable[string]{ - cases: []ConfigurableCase[string]{{ - value: &parser.String{ - LiteralPos: scanner.Position{ - Offset: 25, - Line: 2, - Column: 25, - }, - Value: "asdf", - }, - }}, + Foo: newConfigurableWithPropertyName( + "foo", + nil, + []ConfigurableCase[string]{{ + value: &parser.String{ + LiteralPos: scanner.Position{ + Offset: 25, + Line: 2, + Column: 25, + }, + Value: "asdf", }, - }, - }, - Bar: Configurable[bool]{ - propertyName: "bar", - inner: &configurableInner[bool]{ - single: singleConfigurable[bool]{ - cases: []ConfigurableCase[bool]{{ - value: &parser.Bool{ - LiteralPos: scanner.Position{ - Offset: 54, - Line: 3, - Column: 23, - }, - Value: true, - Token: "true", - }, - }}, + }}, + false, + ), + Bar: newConfigurableWithPropertyName( + "bar", + nil, + []ConfigurableCase[bool]{{ + value: &parser.Bool{ + LiteralPos: scanner.Position{ + Offset: 54, + Line: 3, + Column: 23, + }, + Value: true, + Token: "true", }, - }, - }, + }}, + false, + ), }, }, },