282 lines
7.8 KiB
Go
282 lines
7.8 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"
|
||
|
|
||
|
"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
|
||
|
}
|
||
|
}
|