platform_build_blueprint/proptools/configurable.go

282 lines
7.8 KiB
Go
Raw Normal View History

// 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"
"github.com/google/blueprint/parser"
)
const default_select_branch_name = "__soong_conditions_default__"
type ConfigurableElements interface {
string | bool | []string
}
type ConfigurableEvaluator interface {
EvaluateConfiguration(parser.SelectType, string) (string, bool)
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()
// 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
typ parser.SelectType
condition string
cases map[string]T
appendWrapper *appendWrapper[T]
}
// Ignore the warning about the unused marker variable, it's used via reflection
var _ configurableMarker = Configurable[string]{}.marker
// 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]
}
func (c *Configurable[T]) GetType() parser.SelectType {
return c.typ
}
func (c *Configurable[T]) GetCondition() string {
return c.condition
}
// Evaluate returns the final value for the configurable property.
// A configurable property may be unset, in which case Evaluate will return nil.
func (c *Configurable[T]) Evaluate(evaluator ConfigurableEvaluator) *T {
if c == nil || c.appendWrapper == nil {
return nil
}
return mergeConfiguredValues(
c.evaluateNonTransitive(evaluator),
c.appendWrapper.append.Evaluate(evaluator),
c.propertyName,
evaluator,
)
}
func (c *Configurable[T]) evaluateNonTransitive(evaluator ConfigurableEvaluator) *T {
if c.typ == parser.SelectTypeUnconfigured {
if len(c.cases) == 0 {
return nil
} else if len(c.cases) != 1 {
panic(fmt.Sprintf("Expected 0 or 1 branches in an unconfigured select, found %d", len(c.cases)))
}
result, ok := c.cases[default_select_branch_name]
if !ok {
actual := ""
for k := range c.cases {
actual = k
}
panic(fmt.Sprintf("Expected the single branch of an unconfigured select to be %q, got %q", default_select_branch_name, actual))
}
return &result
}
val, defined := evaluator.EvaluateConfiguration(c.typ, c.condition)
if !defined {
if result, ok := c.cases[default_select_branch_name]; ok {
return &result
}
evaluator.PropertyErrorf(c.propertyName, "%s %q was not defined", c.typ.String(), c.condition)
return nil
}
if val == default_select_branch_name {
panic("Evaluator cannot return the default branch")
}
if result, ok := c.cases[val]; ok {
return &result
}
if result, ok := c.cases[default_select_branch_name]; ok {
return &result
}
evaluator.PropertyErrorf(c.propertyName, "%s %q had value %q, which was not handled by the select statement", c.typ.String(), c.condition, val)
return nil
}
func mergeConfiguredValues[T ConfigurableElements](a, b *T, propertyName string, evalutor ConfigurableEvaluator) *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:
numNonNil := 0
var nonNil *T
if a != nil {
numNonNil += 1
nonNil = a
}
if b != nil {
numNonNil += 1
nonNil = b
}
if numNonNil == 1 {
return nonNil
} else {
evalutor.PropertyErrorf(propertyName, "Cannot append bools")
return nil
}
default:
panic("Should be unreachable")
}
}
// 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)
configuredType() reflect.Type
cloneToReflectValuePtr() reflect.Value
isEmpty() bool
}
// 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(propertyName string, typ parser.SelectType, condition string, cases any)
}
var _ configurableReflection = Configurable[string]{}
var _ configurablePtrReflection = &Configurable[string]{}
func (c *Configurable[T]) initialize(propertyName string, typ parser.SelectType, condition string, cases any) {
c.propertyName = propertyName
c.typ = typ
c.condition = condition
c.cases = cases.(map[string]T)
c.appendWrapper = &appendWrapper[T]{}
}
func (c Configurable[T]) setAppend(append any) {
if c.appendWrapper.append.isEmpty() {
c.appendWrapper.append = append.(Configurable[T])
} else {
c.appendWrapper.append.setAppend(append)
}
}
func (c Configurable[T]) isEmpty() bool {
if c.appendWrapper != nil && !c.appendWrapper.append.isEmpty() {
return false
}
return c.typ == parser.SelectTypeUnconfigured && len(c.cases) == 0
}
func (c Configurable[T]) configuredType() reflect.Type {
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()
}
}
casesCopy := make(map[string]T, len(c.cases))
for k, v := range c.cases {
casesCopy[k] = copyConfiguredValue(v)
}
return &Configurable[T]{
propertyName: c.propertyName,
typ: c.typ,
condition: c.condition,
cases: casesCopy,
appendWrapper: inner,
}
}
func copyConfiguredValue[T ConfigurableElements](t T) T {
switch t2 := any(t).(type) {
case []string:
return any(slices.Clone(t2)).(T)
default:
return t
}
}