Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 191c25f5 authored by Martin Stjernholm's avatar Martin Stjernholm
Browse files

Handle property structs and BpPropertySets as values to AddProperty.

Both will create a nested property set, that may be merged with an
existing one.

Test: m nothing
Bug: 151303681
Change-Id: I30696ba3eb8960ca6fa54c9ee2cf6229ab9f5da9
parent a9a99bc6
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -237,9 +237,25 @@ type BpPropertySet interface {
	// * string
	// * array of the above
	// * bool
	// For these types it is an error if multiple properties with the same name
	// are added.
	//
	// * pointer to a struct
	// * BpPropertySet
	//
	// It is an error if multiple properties with the same name are added.
	// A pointer to a Blueprint-style property struct is first converted into a
	// BpPropertySet by traversing the fields and adding their values as
	// properties in a BpPropertySet. A field with a struct value is itself
	// converted into a BpPropertySet before adding.
	//
	// Adding a BpPropertySet is done as follows:
	// * If no property with the name exists then the BpPropertySet is added
	//   directly to this property. Care must be taken to ensure that it does not
	//   introduce a cycle.
	// * If a property exists with the name and the current value is a
	//   BpPropertySet then every property of the new BpPropertySet is added to
	//   the existing BpPropertySet.
	// * Otherwise, if a property exists with the name then it is an error.
	AddProperty(name string, value interface{})

	// Add a property with an associated tag
+79 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@ package sdk

import (
	"fmt"
	"reflect"
	"strings"

	"android/soong/android"
)
@@ -33,7 +35,82 @@ func (s *bpPropertySet) init() {
	s.tags = make(map[string]android.BpPropertyTag)
}

// Converts the given value, which is assumed to be a struct, to a
// bpPropertySet.
func convertToPropertySet(value reflect.Value) *bpPropertySet {
	res := newPropertySet()
	structType := value.Type()

	for i := 0; i < structType.NumField(); i++ {
		field := structType.Field(i)
		fieldVal := value.Field(i)

		switch fieldVal.Type().Kind() {
		case reflect.Ptr:
			if fieldVal.IsNil() {
				continue // nil pointer means the property isn't set.
			}
			fieldVal = fieldVal.Elem()
		case reflect.Slice:
			if fieldVal.IsNil() {
				continue // Ignore a nil slice (but not one with length zero).
			}
		}

		if fieldVal.Type().Kind() == reflect.Struct {
			fieldVal = fieldVal.Addr() // Avoid struct copy below.
		}
		res.AddProperty(strings.ToLower(field.Name), fieldVal.Interface())
	}

	return res
}

// Converts the given value to something that can be set in a property.
func coercePropertyValue(value interface{}) interface{} {
	val := reflect.ValueOf(value)
	switch val.Kind() {
	case reflect.Struct:
		// convertToPropertySet requires an addressable struct, and this is probably
		// a mistake.
		panic(fmt.Sprintf("Value is a struct, not a pointer to one: %v", value))
	case reflect.Ptr:
		if _, ok := value.(*bpPropertySet); !ok {
			derefValue := reflect.Indirect(val)
			if derefValue.Kind() != reflect.Struct {
				panic(fmt.Sprintf("A pointer must be to a struct, got: %v", value))
			}
			return convertToPropertySet(derefValue)
		}
	}
	return value
}

// Merges the fields of the given property set into s.
func (s *bpPropertySet) mergePropertySet(propSet *bpPropertySet) {
	for _, name := range propSet.order {
		if tag, ok := propSet.tags[name]; ok {
			s.AddPropertyWithTag(name, propSet.properties[name], tag)
		} else {
			s.AddProperty(name, propSet.properties[name])
		}
	}
}

func (s *bpPropertySet) AddProperty(name string, value interface{}) {
	value = coercePropertyValue(value)

	if propSetValue, ok := value.(*bpPropertySet); ok {
		if curValue, ok := s.properties[name]; ok {
			if curSet, ok := curValue.(*bpPropertySet); ok {
				curSet.mergePropertySet(propSetValue)
				return
			}
			// If the current value isn't a property set we got conflicting types.
			// Continue down to the check below to complain about it.
		}
	}

	if s.properties[name] != nil {
		panic(fmt.Sprintf("Property %q already exists in property set", name))
	}
@@ -48,9 +125,8 @@ func (s *bpPropertySet) AddPropertyWithTag(name string, value interface{}, tag a
}

func (s *bpPropertySet) AddPropertySet(name string) android.BpPropertySet {
	set := newPropertySet()
	s.AddProperty(name, set)
	return set
	s.AddProperty(name, newPropertySet())
	return s.properties[name].(android.BpPropertySet)
}

func (s *bpPropertySet) getValue(name string) interface{} {
+134 −0
Original line number Diff line number Diff line
@@ -18,8 +18,142 @@ import (
	"testing"

	"android/soong/android"

	"github.com/google/blueprint/proptools"
)

func propertySetFixture() interface{} {
	set := newPropertySet()
	set.AddProperty("x", "taxi")
	set.AddPropertyWithTag("y", 1729, "tag_y")
	subset := set.AddPropertySet("sub")
	subset.AddPropertyWithTag("x", "taxi", "tag_x")
	subset.AddProperty("y", 1729)
	return set
}

func intPtr(i int) *int { return &i }

type propertyStruct struct {
	X     *string
	Y     *int
	Unset *bool
	Sub   struct {
		X     *string
		Y     *int
		Unset *bool
	}
}

func propertyStructFixture() interface{} {
	str := &propertyStruct{}
	str.X = proptools.StringPtr("taxi")
	str.Y = intPtr(1729)
	str.Sub.X = proptools.StringPtr("taxi")
	str.Sub.Y = intPtr(1729)
	return str
}

func checkPropertySetFixture(h *TestHelper, val interface{}, hasTags bool) {
	set := val.(*bpPropertySet)
	h.AssertDeepEquals("wrong x value", "taxi", set.getValue("x"))
	h.AssertDeepEquals("wrong y value", 1729, set.getValue("y"))

	subset := set.getValue("sub").(*bpPropertySet)
	h.AssertDeepEquals("wrong sub.x value", "taxi", subset.getValue("x"))
	h.AssertDeepEquals("wrong sub.y value", 1729, subset.getValue("y"))

	if hasTags {
		h.AssertDeepEquals("wrong y tag", "tag_y", set.getTag("y"))
		h.AssertDeepEquals("wrong sub.x tag", "tag_x", subset.getTag("x"))
	} else {
		h.AssertDeepEquals("wrong y tag", nil, set.getTag("y"))
		h.AssertDeepEquals("wrong sub.x tag", nil, subset.getTag("x"))
	}
}

func TestAddPropertySimple(t *testing.T) {
	h := &TestHelper{t}
	set := newPropertySet()
	for name, val := range map[string]interface{}{
		"x":   "taxi",
		"y":   1729,
		"t":   true,
		"f":   false,
		"arr": []string{"a", "b", "c"},
	} {
		set.AddProperty(name, val)
		h.AssertDeepEquals("wrong value", val, set.getValue(name))
	}
	h.AssertPanic("adding x again should panic",
		func() { set.AddProperty("x", "taxi") })
	h.AssertPanic("adding arr again should panic",
		func() { set.AddProperty("arr", []string{"d"}) })
}

func TestAddPropertySubset(t *testing.T) {
	h := &TestHelper{t}
	getFixtureMap := map[string]func() interface{}{
		"property set":    propertySetFixture,
		"property struct": propertyStructFixture,
	}

	t.Run("add new subset", func(t *testing.T) {
		for name, getFixture := range getFixtureMap {
			t.Run(name, func(t *testing.T) {
				set := propertySetFixture().(*bpPropertySet)
				set.AddProperty("new", getFixture())
				checkPropertySetFixture(h, set, true)
				checkPropertySetFixture(h, set.getValue("new"), name == "property set")
			})
		}
	})

	t.Run("merge existing subset", func(t *testing.T) {
		for name, getFixture := range getFixtureMap {
			t.Run(name, func(t *testing.T) {
				set := newPropertySet()
				subset := set.AddPropertySet("sub")
				subset.AddProperty("flag", false)
				subset.AddPropertySet("sub")
				set.AddProperty("sub", getFixture())
				merged := set.getValue("sub").(*bpPropertySet)
				h.AssertDeepEquals("wrong flag value", false, merged.getValue("flag"))
				checkPropertySetFixture(h, merged, name == "property set")
			})
		}
	})

	t.Run("add conflicting subset", func(t *testing.T) {
		set := propertySetFixture().(*bpPropertySet)
		h.AssertPanic("adding x again should panic",
			func() { set.AddProperty("x", propertySetFixture()) })
	})

	t.Run("add non-pointer struct", func(t *testing.T) {
		set := propertySetFixture().(*bpPropertySet)
		str := propertyStructFixture().(*propertyStruct)
		h.AssertPanic("adding a non-pointer struct should panic",
			func() { set.AddProperty("new", *str) })
	})
}

func TestAddPropertySetNew(t *testing.T) {
	h := &TestHelper{t}
	set := newPropertySet()
	subset := set.AddPropertySet("sub")
	subset.AddProperty("new", "d^^b")
	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
}

func TestAddPropertySetExisting(t *testing.T) {
	h := &TestHelper{t}
	set := propertySetFixture().(*bpPropertySet)
	subset := set.AddPropertySet("sub")
	subset.AddProperty("new", "d^^b")
	h.AssertDeepEquals("wrong sub.new value", "d^^b", set.getValue("sub").(*bpPropertySet).getValue("new"))
}

type removeFredTransformation struct {
	identityTransformation
}
+16 −0
Original line number Diff line number Diff line
@@ -217,6 +217,22 @@ func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actu
	}
}

func (h *TestHelper) AssertPanic(message string, funcThatShouldPanic func()) {
	h.t.Helper()
	panicked := false
	func() {
		defer func() {
			if x := recover(); x != nil {
				panicked = true
			}
		}()
		funcThatShouldPanic()
	}()
	if !panicked {
		h.t.Error(message)
	}
}

// Encapsulates result of processing an SDK definition. Provides support for
// checking the state of the build structures.
type testSdkResult struct {