c472e38ec1
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
1232 lines
35 KiB
Go
1232 lines
35 KiB
Go
// Copyright 2023 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 proptools
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/blueprint/optional"
|
|
"github.com/google/blueprint/parser"
|
|
)
|
|
|
|
// ConfigurableOptional is the same as ShallowOptional, but we use this separate
|
|
// name to reserve the ability to switch to an alternative implementation later.
|
|
type ConfigurableOptional[T any] struct {
|
|
shallowOptional optional.ShallowOptional[T]
|
|
}
|
|
|
|
// IsPresent returns true if the optional contains a value
|
|
func (o *ConfigurableOptional[T]) IsPresent() bool {
|
|
return o.shallowOptional.IsPresent()
|
|
}
|
|
|
|
// IsEmpty returns true if the optional does not have a value
|
|
func (o *ConfigurableOptional[T]) IsEmpty() bool {
|
|
return o.shallowOptional.IsEmpty()
|
|
}
|
|
|
|
// Get() returns the value inside the optional. It panics if IsEmpty() returns true
|
|
func (o *ConfigurableOptional[T]) Get() T {
|
|
return o.shallowOptional.Get()
|
|
}
|
|
|
|
// GetOrDefault() returns the value inside the optional if IsPresent() returns true,
|
|
// or the provided value otherwise.
|
|
func (o *ConfigurableOptional[T]) GetOrDefault(other T) T {
|
|
return o.shallowOptional.GetOrDefault(other)
|
|
}
|
|
|
|
type ConfigurableElements interface {
|
|
string | bool | []string
|
|
}
|
|
|
|
type ConfigurableEvaluator interface {
|
|
EvaluateConfiguration(condition ConfigurableCondition, property string) ConfigurableValue
|
|
PropertyErrorf(property, fmt string, args ...interface{})
|
|
}
|
|
|
|
// configurableMarker is just so that reflection can check type of the first field of
|
|
// the struct to determine if it is a configurable struct.
|
|
type configurableMarker bool
|
|
|
|
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 {
|
|
functionName 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 {
|
|
var sb strings.Builder
|
|
sb.WriteString(c.functionName)
|
|
sb.WriteRune('(')
|
|
for i, arg := range c.args {
|
|
sb.WriteString(strconv.Quote(arg))
|
|
if i < len(c.args)-1 {
|
|
sb.WriteString(", ")
|
|
}
|
|
}
|
|
sb.WriteRune(')')
|
|
return sb.String()
|
|
}
|
|
|
|
type configurableValueType int
|
|
|
|
const (
|
|
configurableValueTypeString configurableValueType = iota
|
|
configurableValueTypeBool
|
|
configurableValueTypeUndefined
|
|
)
|
|
|
|
func (v *configurableValueType) patternType() configurablePatternType {
|
|
switch *v {
|
|
case configurableValueTypeString:
|
|
return configurablePatternTypeString
|
|
case configurableValueTypeBool:
|
|
return configurablePatternTypeBool
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
func (v *configurableValueType) String() string {
|
|
switch *v {
|
|
case configurableValueTypeString:
|
|
return "string"
|
|
case configurableValueTypeBool:
|
|
return "bool"
|
|
case configurableValueTypeUndefined:
|
|
return "undefined"
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
// ConfigurableValue represents the value of a certain condition being selected on.
|
|
// This type mostly exists to act as a sum type between string, bool, and undefined.
|
|
type ConfigurableValue struct {
|
|
typ configurableValueType
|
|
stringValue string
|
|
boolValue bool
|
|
}
|
|
|
|
func (c *ConfigurableValue) toExpression() parser.Expression {
|
|
switch c.typ {
|
|
case configurableValueTypeBool:
|
|
return &parser.Bool{Value: c.boolValue}
|
|
case configurableValueTypeString:
|
|
return &parser.String{Value: c.stringValue}
|
|
default:
|
|
panic(fmt.Sprintf("Unhandled configurableValueType: %s", c.typ.String()))
|
|
}
|
|
}
|
|
|
|
func (c *ConfigurableValue) String() string {
|
|
switch c.typ {
|
|
case configurableValueTypeString:
|
|
return strconv.Quote(c.stringValue)
|
|
case configurableValueTypeBool:
|
|
if c.boolValue {
|
|
return "true"
|
|
} else {
|
|
return "false"
|
|
}
|
|
case configurableValueTypeUndefined:
|
|
return "undefined"
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
func ConfigurableValueString(s string) ConfigurableValue {
|
|
return ConfigurableValue{
|
|
typ: configurableValueTypeString,
|
|
stringValue: s,
|
|
}
|
|
}
|
|
|
|
func ConfigurableValueBool(b bool) ConfigurableValue {
|
|
return ConfigurableValue{
|
|
typ: configurableValueTypeBool,
|
|
boolValue: b,
|
|
}
|
|
}
|
|
|
|
func ConfigurableValueUndefined() ConfigurableValue {
|
|
return ConfigurableValue{
|
|
typ: configurableValueTypeUndefined,
|
|
}
|
|
}
|
|
|
|
type configurablePatternType int
|
|
|
|
const (
|
|
configurablePatternTypeString configurablePatternType = iota
|
|
configurablePatternTypeBool
|
|
configurablePatternTypeDefault
|
|
configurablePatternTypeAny
|
|
)
|
|
|
|
func (v *configurablePatternType) String() string {
|
|
switch *v {
|
|
case configurablePatternTypeString:
|
|
return "string"
|
|
case configurablePatternTypeBool:
|
|
return "bool"
|
|
case configurablePatternTypeDefault:
|
|
return "default"
|
|
case configurablePatternTypeAny:
|
|
return "any"
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
typ configurablePatternType
|
|
stringValue string
|
|
boolValue bool
|
|
binding string
|
|
}
|
|
|
|
func NewStringConfigurablePattern(s string) ConfigurablePattern {
|
|
return ConfigurablePattern{
|
|
typ: configurablePatternTypeString,
|
|
stringValue: s,
|
|
}
|
|
}
|
|
|
|
func NewBoolConfigurablePattern(b bool) ConfigurablePattern {
|
|
return ConfigurablePattern{
|
|
typ: configurablePatternTypeBool,
|
|
boolValue: b,
|
|
}
|
|
}
|
|
|
|
func NewDefaultConfigurablePattern() ConfigurablePattern {
|
|
return ConfigurablePattern{
|
|
typ: configurablePatternTypeDefault,
|
|
}
|
|
}
|
|
|
|
func (p *ConfigurablePattern) matchesValue(v ConfigurableValue) bool {
|
|
if p.typ == configurablePatternTypeDefault {
|
|
return true
|
|
}
|
|
if v.typ == configurableValueTypeUndefined {
|
|
return false
|
|
}
|
|
if p.typ == configurablePatternTypeAny {
|
|
return true
|
|
}
|
|
if p.typ != v.typ.patternType() {
|
|
return false
|
|
}
|
|
switch p.typ {
|
|
case configurablePatternTypeString:
|
|
return p.stringValue == v.stringValue
|
|
case configurablePatternTypeBool:
|
|
return p.boolValue == v.boolValue
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
|
|
func (p *ConfigurablePattern) matchesValueType(v ConfigurableValue) bool {
|
|
if p.typ == configurablePatternTypeDefault {
|
|
return true
|
|
}
|
|
if v.typ == configurableValueTypeUndefined {
|
|
return true
|
|
}
|
|
if p.typ == configurablePatternTypeAny {
|
|
return true
|
|
}
|
|
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 {
|
|
patterns []ConfigurablePattern
|
|
value parser.Expression
|
|
}
|
|
|
|
type configurableCaseReflection interface {
|
|
initialize(patterns []ConfigurablePattern, value parser.Expression)
|
|
}
|
|
|
|
var _ configurableCaseReflection = &ConfigurableCase[string]{}
|
|
|
|
func NewConfigurableCase[T ConfigurableElements](patterns []ConfigurablePattern, value *T) ConfigurableCase[T] {
|
|
var valueExpr parser.Expression
|
|
if value == nil {
|
|
valueExpr = &parser.UnsetProperty{}
|
|
} else {
|
|
switch v := any(value).(type) {
|
|
case *string:
|
|
valueExpr = &parser.String{Value: *v}
|
|
case *bool:
|
|
valueExpr = &parser.Bool{Value: *v}
|
|
case *[]string:
|
|
innerValues := make([]parser.Expression, 0, len(*v))
|
|
for _, x := range *v {
|
|
innerValues = append(innerValues, &parser.String{Value: x})
|
|
}
|
|
valueExpr = &parser.List{Values: innerValues}
|
|
default:
|
|
panic(fmt.Sprintf("should be unreachable due to the ConfigurableElements restriction: %#v", value))
|
|
}
|
|
}
|
|
// Clone the values so they can't be modified from soong
|
|
patterns = slices.Clone(patterns)
|
|
return ConfigurableCase[T]{
|
|
patterns: patterns,
|
|
value: valueExpr,
|
|
}
|
|
}
|
|
|
|
func (c *ConfigurableCase[T]) initialize(patterns []ConfigurablePattern, value parser.Expression) {
|
|
c.patterns = patterns
|
|
c.value = value
|
|
}
|
|
|
|
// for the given T, return the reflect.type of configurableCase[T]
|
|
func configurableCaseType(configuredType reflect.Type) reflect.Type {
|
|
// 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(ConfigurableCase[string]{})
|
|
case reflect.Bool:
|
|
return reflect.TypeOf(ConfigurableCase[bool]{})
|
|
case reflect.Slice:
|
|
switch configuredType.Elem().Kind() {
|
|
case reflect.String:
|
|
return reflect.TypeOf(ConfigurableCase[[]string]{})
|
|
}
|
|
}
|
|
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,
|
|
// in order to allow select statements to be used in bp files
|
|
// for that property. For example, for the property struct:
|
|
//
|
|
// my_props {
|
|
// Property_a: string,
|
|
// Property_b: Configurable[string],
|
|
// }
|
|
//
|
|
// property_b can then use select statements:
|
|
//
|
|
// my_module {
|
|
// property_a: "foo"
|
|
// property_b: select(soong_config_variable("my_namespace", "my_variable"), {
|
|
// "value_1": "bar",
|
|
// "value_2": "baz",
|
|
// default: "qux",
|
|
// })
|
|
// }
|
|
//
|
|
// The configurable property holds all the branches of the select
|
|
// statement in the bp file. To extract the final value, you must
|
|
// call Evaluate() on the configurable property.
|
|
//
|
|
// All configurable properties support being unset, so there is
|
|
// no need to use a pointer type like Configurable[*string].
|
|
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 {
|
|
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]
|
|
scope *parser.Scope
|
|
}
|
|
|
|
// Ignore the warning about the unused marker variable, it's used via reflection
|
|
var _ configurableMarker = Configurable[string]{}.marker
|
|
|
|
func NewConfigurable[T ConfigurableElements](conditions []ConfigurableCondition, cases []ConfigurableCase[T]) Configurable[T] {
|
|
for _, c := range cases {
|
|
if len(c.patterns) != len(conditions) {
|
|
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)
|
|
var zeroPostProcessors [][]postProcessor[T]
|
|
return Configurable[T]{
|
|
inner: &configurableInner[T]{
|
|
single: singleConfigurable[T]{
|
|
conditions: conditions,
|
|
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.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.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.evaluate(c.propertyName, evaluator)
|
|
if result != nil {
|
|
// Copy the result so that it can't be changed from soong
|
|
return copyConfiguredValue(*result)
|
|
}
|
|
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
|
|
}
|
|
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 {
|
|
if len(c.conditions) != 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
|
|
}
|
|
}
|
|
if len(c.conditions) == 0 {
|
|
if len(c.cases) == 0 {
|
|
return nil
|
|
} else if len(c.cases) == 1 {
|
|
if result, err := expressionToConfiguredValue[T](c.cases[0].value, c.scope); err != nil {
|
|
evaluator.PropertyErrorf(propertyName, "%s", err.Error())
|
|
return nil
|
|
} else {
|
|
return result
|
|
}
|
|
} else {
|
|
evaluator.PropertyErrorf(propertyName, "Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases))
|
|
return nil
|
|
}
|
|
}
|
|
values := make([]ConfigurableValue, len(c.conditions))
|
|
for i, condition := range c.conditions {
|
|
values[i] = evaluator.EvaluateConfiguration(condition, propertyName)
|
|
}
|
|
foundMatch := false
|
|
nonMatchingIndex := 0
|
|
var result *T
|
|
for _, case_ := range c.cases {
|
|
allMatch := true
|
|
for i, pat := range case_.patterns {
|
|
if !pat.matchesValueType(values[i]) {
|
|
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
|
|
}
|
|
if !pat.matchesValue(values[i]) {
|
|
allMatch = false
|
|
nonMatchingIndex = i
|
|
break
|
|
}
|
|
}
|
|
if allMatch && !foundMatch {
|
|
newScope := createScopeWithBindings(c.scope, case_.patterns, values)
|
|
if r, err := expressionToConfiguredValue[T](case_.value, newScope); err != nil {
|
|
evaluator.PropertyErrorf(propertyName, "%s", err.Error())
|
|
return nil
|
|
} else {
|
|
result = r
|
|
}
|
|
foundMatch = true
|
|
}
|
|
}
|
|
if foundMatch {
|
|
return result
|
|
}
|
|
|
|
evaluator.PropertyErrorf(propertyName, "%s had value %s, which was not handled by the select statement", c.conditions[nonMatchingIndex].String(), values[nonMatchingIndex].String())
|
|
return nil
|
|
}
|
|
|
|
func createScopeWithBindings(parent *parser.Scope, patterns []ConfigurablePattern, values []ConfigurableValue) *parser.Scope {
|
|
result := parent
|
|
for i, pattern := range patterns {
|
|
if pattern.binding != "" {
|
|
if result == parent {
|
|
result = parser.NewScope(parent)
|
|
}
|
|
err := result.HandleAssignment(&parser.Assignment{
|
|
Name: pattern.binding,
|
|
Value: values[i].toExpression(),
|
|
Assigner: "=",
|
|
})
|
|
if err != nil {
|
|
// This shouldn't happen due to earlier validity checks
|
|
panic(err.Error())
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func appendConfiguredValues[T ConfigurableElements](a, b *T) *T {
|
|
if a == nil && b == nil {
|
|
return nil
|
|
}
|
|
switch any(a).(type) {
|
|
case *[]string:
|
|
var a2 []string
|
|
var b2 []string
|
|
if a != nil {
|
|
a2 = *any(a).(*[]string)
|
|
}
|
|
if b != nil {
|
|
b2 = *any(b).(*[]string)
|
|
}
|
|
result := make([]string, len(a2)+len(b2))
|
|
idx := 0
|
|
for i := 0; i < len(a2); i++ {
|
|
result[idx] = a2[i]
|
|
idx += 1
|
|
}
|
|
for i := 0; i < len(b2); i++ {
|
|
result[idx] = b2[i]
|
|
idx += 1
|
|
}
|
|
return any(&result).(*T)
|
|
case *string:
|
|
a := String(any(a).(*string))
|
|
b := String(any(b).(*string))
|
|
result := a + b
|
|
return any(&result).(*T)
|
|
case *bool:
|
|
// Addition of bools will OR them together. This is inherited behavior
|
|
// from how proptools.ExtendBasicType works with non-configurable bools.
|
|
result := false
|
|
if a != nil {
|
|
result = result || *any(a).(*bool)
|
|
}
|
|
if b != nil {
|
|
result = result || *any(b).(*bool)
|
|
}
|
|
return any(&result).(*T)
|
|
default:
|
|
panic("Should be unreachable")
|
|
}
|
|
}
|
|
|
|
func replaceConfiguredValues[T ConfigurableElements](a, b *T) *T {
|
|
if b != nil {
|
|
return b
|
|
}
|
|
return a
|
|
}
|
|
|
|
// configurableReflection is an interface that exposes some methods that are
|
|
// helpful when working with reflect.Values of Configurable objects, used by
|
|
// 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.
|
|
type configurableReflection interface {
|
|
setAppend(append any, replace bool, prepend bool)
|
|
configuredType() reflect.Type
|
|
clone() any
|
|
isEmpty() bool
|
|
printfInto(value string) error
|
|
}
|
|
|
|
// Same as configurableReflection, but since initialize needs to take a pointer
|
|
// to a Configurable, it was broken out into a separate interface.
|
|
type configurablePtrReflection interface {
|
|
initialize(scope *parser.Scope, propertyName string, conditions []ConfigurableCondition, cases any)
|
|
}
|
|
|
|
var _ configurableReflection = Configurable[string]{}
|
|
var _ configurablePtrReflection = &Configurable[string]{}
|
|
|
|
func (c *Configurable[T]) initialize(scope *parser.Scope, propertyName string, conditions []ConfigurableCondition, cases any) {
|
|
c.propertyName = propertyName
|
|
c.inner = &configurableInner[T]{
|
|
single: singleConfigurable[T]{
|
|
conditions: conditions,
|
|
cases: cases.([]ConfigurableCase[T]),
|
|
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) {
|
|
a := append.(Configurable[T])
|
|
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()
|
|
} 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 *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
|
|
c.next = &configurableInner[T]{
|
|
single: singleConfigurable[T]{
|
|
cases: []ConfigurableCase[T]{{
|
|
value: configuredValueToExpression(value),
|
|
}},
|
|
},
|
|
}
|
|
} else {
|
|
c.next.appendSimpleValue(value)
|
|
}
|
|
}
|
|
|
|
func (c Configurable[T]) printfInto(value string) error {
|
|
return c.inner.printfInto(value)
|
|
}
|
|
|
|
func (c *configurableInner[T]) printfInto(value string) error {
|
|
for c != nil {
|
|
if err := c.single.printfInto(value); err != nil {
|
|
return err
|
|
}
|
|
c = c.next
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *singleConfigurable[T]) printfInto(value string) error {
|
|
for _, c := range c.cases {
|
|
if c.value == nil {
|
|
continue
|
|
}
|
|
if err := c.value.PrintfInto(value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c Configurable[T]) clone() any {
|
|
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] {
|
|
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 {
|
|
return c.inner.isEmpty()
|
|
}
|
|
|
|
func (c *singleConfigurable[T]) isEmpty() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
if len(c.cases) > 1 {
|
|
return false
|
|
}
|
|
if len(c.cases) == 1 && c.cases[0].value != nil {
|
|
if _, ok := c.cases[0].value.(*parser.UnsetProperty); ok {
|
|
return true
|
|
}
|
|
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 _, isUnset := c.value.(*parser.UnsetProperty); isUnset || c.value == nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (c Configurable[T]) configuredType() reflect.Type {
|
|
return reflect.TypeOf((*T)(nil)).Elem()
|
|
}
|
|
|
|
func expressionToConfiguredValue[T ConfigurableElements](expr parser.Expression, scope *parser.Scope) (*T, error) {
|
|
expr, err := expr.Eval(scope)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch e := expr.(type) {
|
|
case *parser.UnsetProperty:
|
|
return nil, nil
|
|
case *parser.String:
|
|
if result, ok := any(&e.Value).(*T); ok {
|
|
return result, nil
|
|
} else {
|
|
return nil, fmt.Errorf("can't assign string value to %s property", configuredTypeToString[T]())
|
|
}
|
|
case *parser.Bool:
|
|
if result, ok := any(&e.Value).(*T); ok {
|
|
return result, nil
|
|
} else {
|
|
return nil, fmt.Errorf("can't assign bool value to %s property", configuredTypeToString[T]())
|
|
}
|
|
case *parser.List:
|
|
result := make([]string, 0, len(e.Values))
|
|
for _, x := range e.Values {
|
|
if y, ok := x.(*parser.String); ok {
|
|
result = append(result, y.Value)
|
|
} else {
|
|
return nil, fmt.Errorf("expected list of strings but found list of %s", x.Type())
|
|
}
|
|
}
|
|
if result, ok := any(&result).(*T); ok {
|
|
return result, nil
|
|
} else {
|
|
return nil, fmt.Errorf("can't assign list of strings to list of %s property", configuredTypeToString[T]())
|
|
}
|
|
default:
|
|
// If the expression was not evaluated beforehand we could hit this error even when the types match,
|
|
// but that's an internal logic error.
|
|
return nil, fmt.Errorf("expected %s but found %s (%#v)", configuredTypeToString[T](), expr.Type().String(), expr)
|
|
}
|
|
}
|
|
|
|
func configuredValueToExpression[T ConfigurableElements](value T) parser.Expression {
|
|
switch v := any(value).(type) {
|
|
case string:
|
|
return &parser.String{Value: v}
|
|
case bool:
|
|
return &parser.Bool{Value: v}
|
|
case []string:
|
|
values := make([]parser.Expression, 0, len(v))
|
|
for _, x := range v {
|
|
values = append(values, &parser.String{Value: x})
|
|
}
|
|
return &parser.List{Values: values}
|
|
default:
|
|
panic("unhandled type in configuredValueToExpression")
|
|
}
|
|
}
|
|
|
|
func configuredTypeToString[T ConfigurableElements]() string {
|
|
var zero T
|
|
switch any(zero).(type) {
|
|
case string:
|
|
return "string"
|
|
case bool:
|
|
return "bool"
|
|
case []string:
|
|
return "list of strings"
|
|
default:
|
|
panic("should be unreachable")
|
|
}
|
|
}
|
|
|
|
func copyConfiguredValue[T ConfigurableElements](t T) T {
|
|
switch t2 := any(t).(type) {
|
|
case []string:
|
|
return any(slices.Clone(t2)).(T)
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
func configuredValuePtrToOptional[T ConfigurableElements](t *T) ConfigurableOptional[T] {
|
|
if t == nil {
|
|
return ConfigurableOptional[T]{optional.NewShallowOptional(t)}
|
|
}
|
|
switch t2 := any(*t).(type) {
|
|
case []string:
|
|
result := any(slices.Clone(t2)).(T)
|
|
return ConfigurableOptional[T]{optional.NewShallowOptional(&result)}
|
|
default:
|
|
return ConfigurableOptional[T]{optional.NewShallowOptional(t)}
|
|
}
|
|
}
|
|
|
|
// PrintfIntoConfigurable replaces %s occurrences in strings in Configurable properties
|
|
// with the provided string value. It's intention is to support soong config value variables
|
|
// on Configurable properties.
|
|
func PrintfIntoConfigurable(c any, value string) error {
|
|
return c.(configurableReflection).printfInto(value)
|
|
}
|
|
|
|
func promoteValueToConfigurable(origional reflect.Value) reflect.Value {
|
|
var expr parser.Expression
|
|
var kind reflect.Kind
|
|
if origional.Kind() == reflect.Pointer && origional.IsNil() {
|
|
expr = &parser.UnsetProperty{}
|
|
kind = origional.Type().Elem().Kind()
|
|
} else {
|
|
if origional.Kind() == reflect.Pointer {
|
|
origional = origional.Elem()
|
|
}
|
|
kind = origional.Kind()
|
|
switch kind {
|
|
case reflect.String:
|
|
expr = &parser.String{Value: origional.String()}
|
|
case reflect.Bool:
|
|
expr = &parser.Bool{Value: origional.Bool()}
|
|
case reflect.Slice:
|
|
strList := origional.Interface().([]string)
|
|
exprList := make([]parser.Expression, 0, len(strList))
|
|
for _, x := range strList {
|
|
exprList = append(exprList, &parser.String{Value: x})
|
|
}
|
|
expr = &parser.List{Values: exprList}
|
|
default:
|
|
panic("can only convert string/bool/[]string to configurable")
|
|
}
|
|
}
|
|
switch kind {
|
|
case reflect.String:
|
|
return reflect.ValueOf(Configurable[string]{
|
|
inner: &configurableInner[string]{
|
|
single: singleConfigurable[string]{
|
|
cases: []ConfigurableCase[string]{{
|
|
value: expr,
|
|
}},
|
|
},
|
|
},
|
|
postProcessors: &[][]postProcessor[string]{},
|
|
})
|
|
case reflect.Bool:
|
|
return reflect.ValueOf(Configurable[bool]{
|
|
inner: &configurableInner[bool]{
|
|
single: singleConfigurable[bool]{
|
|
cases: []ConfigurableCase[bool]{{
|
|
value: expr,
|
|
}},
|
|
},
|
|
},
|
|
postProcessors: &[][]postProcessor[bool]{},
|
|
})
|
|
case reflect.Slice:
|
|
return reflect.ValueOf(Configurable[[]string]{
|
|
inner: &configurableInner[[]string]{
|
|
single: singleConfigurable[[]string]{
|
|
cases: []ConfigurableCase[[]string]{{
|
|
value: expr,
|
|
}},
|
|
},
|
|
},
|
|
postProcessors: &[][]postProcessor[[]string]{},
|
|
})
|
|
default:
|
|
panic(fmt.Sprintf("Can't convert %s property to a configurable", origional.Kind().String()))
|
|
}
|
|
}
|