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

Commit 362051ee authored by Colin Cross's avatar Colin Cross Committed by android-build-merger
Browse files

Merge "Filter product variable property structs"

am: 2334757d

Change-Id: Iee8f1d0cb3ac2e01032b569727f9ed48e6783273
parents 2e7e4df5 2334757d
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -514,8 +514,19 @@ func InitAndroidModule(m Module) {

	m.AddProperties(
		&base.nameProperties,
		&base.commonProperties,
		&base.variableProperties)
		&base.commonProperties)

	// Allow tests to override the default product variables
	if base.variableProperties == nil {
		base.variableProperties = zeroProductVariables
	}

	// Filter the product variables properties to the ones that exist on this module
	base.variableProperties = createVariableProperties(m.GetProperties(), base.variableProperties)
	if base.variableProperties != nil {
		m.AddProperties(base.variableProperties)
	}

	base.generalProperties = m.GetProperties()
	base.customizableProperties = m.GetProperties()

@@ -597,7 +608,7 @@ type ModuleBase struct {

	nameProperties          nameProperties
	commonProperties        commonProperties
	variableProperties      variableProperties
	variableProperties      interface{}
	hostAndDeviceProperties hostAndDeviceProperties
	generalProperties       []interface{}
	archProperties          [][]interface{}
+18 −1
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
package android

import (
	"reflect"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"
)
@@ -244,8 +246,23 @@ func (t *topDownMutatorContext) Rename(name string) {
}

func (t *topDownMutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
	inherited := []interface{}{&t.Module().base().commonProperties, &t.Module().base().variableProperties}
	inherited := []interface{}{&t.Module().base().commonProperties}
	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), append(inherited, props...)...).(Module)

	if t.Module().base().variableProperties != nil && module.base().variableProperties != nil {
		src := t.Module().base().variableProperties
		dst := []interface{}{
			module.base().variableProperties,
			// Put an empty copy of the src properties into dst so that properties in src that are not in dst
			// don't cause a "failed to find property to extend" error.
			proptools.CloneEmptyProperties(reflect.ValueOf(src).Elem()).Interface(),
		}
		err := proptools.AppendMatchingProperties(dst, src, nil)
		if err != nil {
			panic(err)
		}
	}

	return module
}

+111 −3
Original line number Diff line number Diff line
@@ -122,7 +122,7 @@ type variableProperties struct {
	} `android:"arch_variant"`
}

var zeroProductVariables variableProperties
var zeroProductVariables interface{} = variableProperties{}

type productVariables struct {
	// Suffix to add to generated Makefiles
@@ -366,8 +366,13 @@ func variableMutator(mctx BottomUpMutatorContext) {

	// TODO: depend on config variable, create variants, propagate variants up tree
	a := module.base()
	variableValues := reflect.ValueOf(&a.variableProperties.Product_variables).Elem()
	zeroValues := reflect.ValueOf(zeroProductVariables.Product_variables)

	if a.variableProperties == nil {
		return
	}

	variableValues := reflect.ValueOf(a.variableProperties).Elem().FieldByName("Product_variables")
	zeroValues := reflect.ValueOf(zeroProductVariables).FieldByName("Product_variables")

	for i := 0; i < variableValues.NumField(); i++ {
		variableValue := variableValues.Field(i)
@@ -496,3 +501,106 @@ func printfIntoProperty(propertyValue reflect.Value, variableValue interface{})

	return nil
}

var variablePropTypeMap OncePer

// sliceToTypeArray takes a slice of property structs and returns a reflection created array containing the
// reflect.Types of each property struct.  The result can be used as a key in a map.
func sliceToTypeArray(s []interface{}) interface{} {
	// Create an array using reflection whose length is the length of the input slice
	ret := reflect.New(reflect.ArrayOf(len(s), reflect.TypeOf(reflect.TypeOf(0)))).Elem()
	for i, e := range s {
		ret.Index(i).Set(reflect.ValueOf(reflect.TypeOf(e)))
	}
	return ret.Interface()
}

// createVariableProperties takes the list of property structs for a module and returns a property struct that
// contains the product variable properties that exist in the property structs, or nil if there are none.  It
// caches the result.
func createVariableProperties(moduleTypeProps []interface{}, productVariables interface{}) interface{} {
	// Convert the moduleTypeProps to an array of reflect.Types that can be used as a key in the OncePer.
	key := sliceToTypeArray(moduleTypeProps)

	// Use the variablePropTypeMap OncePer to cache the result for each set of property struct types.
	typ, _ := variablePropTypeMap.Once(NewCustomOnceKey(key), func() interface{} {
		// Compute the filtered property struct type.
		return createVariablePropertiesType(moduleTypeProps, productVariables)
	}).(reflect.Type)

	if typ == nil {
		return nil
	}

	// Create a new pointer to a filtered property struct.
	return reflect.New(typ).Interface()
}

// createVariablePropertiesType creates a new type that contains only the product variable properties that exist in
// a list of property structs.
func createVariablePropertiesType(moduleTypeProps []interface{}, productVariables interface{}) reflect.Type {
	typ, _ := proptools.FilterPropertyStruct(reflect.TypeOf(productVariables),
		func(field reflect.StructField, prefix string) (bool, reflect.StructField) {
			// Filter function, returns true if the field should be in the resulting struct
			if prefix == "" {
				// Keep the top level Product_variables field
				return true, field
			}
			_, rest := splitPrefix(prefix)
			if rest == "" {
				// Keep the 2nd level field (i.e. Product_variables.Eng)
				return true, field
			}

			// Strip off the first 2 levels of the prefix
			_, prefix = splitPrefix(rest)

			for _, p := range moduleTypeProps {
				if fieldExistsByNameRecursive(reflect.TypeOf(p).Elem(), prefix, field.Name) {
					// Keep any fields that exist in one of the property structs
					return true, field
				}
			}

			return false, field
		})
	return typ
}

func splitPrefix(prefix string) (first, rest string) {
	index := strings.IndexByte(prefix, '.')
	if index == -1 {
		return prefix, ""
	}
	return prefix[:index], prefix[index+1:]
}

func fieldExistsByNameRecursive(t reflect.Type, prefix, name string) bool {
	if t.Kind() != reflect.Struct {
		panic(fmt.Errorf("fieldExistsByNameRecursive can only be called on a reflect.Struct"))
	}

	if prefix != "" {
		split := strings.SplitN(prefix, ".", 2)
		firstPrefix := split[0]
		rest := ""
		if len(split) > 1 {
			rest = split[1]
		}
		f, exists := t.FieldByName(firstPrefix)
		if !exists {
			return false
		}
		ft := f.Type
		if ft.Kind() == reflect.Ptr {
			ft = ft.Elem()
		}
		if ft.Kind() != reflect.Struct {
			panic(fmt.Errorf("field %q in %q is not a struct", firstPrefix, t))
		}
		return fieldExistsByNameRecursive(ft, rest, name)
	} else {
		_, exists := t.FieldByName(name)
		return exists
	}
}
+111 −0
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@ package android

import (
	"reflect"
	"strconv"
	"testing"

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

type printfIntoPropertyTestCase struct {
@@ -122,3 +125,111 @@ func TestPrintfIntoProperty(t *testing.T) {
		}
	}
}

type testProductVariableModule struct {
	ModuleBase
}

func (m *testProductVariableModule) GenerateAndroidBuildActions(ctx ModuleContext) {
}

var testProductVariableProperties = struct {
	Product_variables struct {
		Eng struct {
			Srcs   []string
			Cflags []string
		}
	}
}{}

func testProductVariableModuleFactoryFactory(props interface{}) func() Module {
	return func() Module {
		m := &testProductVariableModule{}
		clonedProps := proptools.CloneProperties(reflect.ValueOf(props)).Interface()
		m.AddProperties(clonedProps)

		// Set a default variableProperties, this will be used as the input to the property struct filter
		// for this test module.
		m.variableProperties = testProductVariableProperties
		InitAndroidModule(m)
		return m
	}
}

func TestProductVariables(t *testing.T) {
	ctx := NewTestContext()
	// A module type that has a srcs property but not a cflags property.
	ctx.RegisterModuleType("module1", ModuleFactoryAdaptor(testProductVariableModuleFactoryFactory(struct {
		Srcs []string
	}{})))
	// A module type that has a cflags property but not a srcs property.
	ctx.RegisterModuleType("module2", ModuleFactoryAdaptor(testProductVariableModuleFactoryFactory(struct {
		Cflags []string
	}{})))
	// A module type that does not have any properties that match product_variables.
	ctx.RegisterModuleType("module3", ModuleFactoryAdaptor(testProductVariableModuleFactoryFactory(struct {
		Foo []string
	}{})))
	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
		ctx.BottomUp("variable", variableMutator).Parallel()
	})

	// Test that a module can use one product variable even if it doesn't have all the properties
	// supported by that product variable.
	bp := `
		module1 {
			name: "foo",
			product_variables: {
				eng: {
					srcs: ["foo.c"],
				},
			},
		}
		module2 {
			name: "bar",
			product_variables: {
				eng: {
					cflags: ["-DBAR"],
				},
			},
		}

		module3 {
			name: "baz",
		}
	`

	mockFS := map[string][]byte{
		"Android.bp": []byte(bp),
	}

	ctx.MockFileSystem(mockFS)

	ctx.Register()

	config := TestConfig(buildDir, nil)
	config.TestProductVariables.Eng = proptools.BoolPtr(true)

	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
	FailIfErrored(t, errs)
	_, errs = ctx.PrepareBuildActions(config)
	FailIfErrored(t, errs)
}

func BenchmarkSliceToTypeArray(b *testing.B) {
	for _, n := range []int{1, 2, 4, 8, 100} {
		var propStructs []interface{}
		for i := 0; i < n; i++ {
			propStructs = append(propStructs, &struct {
				A *string
				B string
			}{})

		}
		b.Run(strconv.Itoa(n), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				_ = sliceToTypeArray(propStructs)
			}
		})
	}
}