From 1b48842a4b83ba6234d26ff4c77a0884f5008f62 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 4 Mar 2019 22:33:56 -0800 Subject: Add path properties mutator Add a mutator pass after DepsMutator that visits every property struct in every module looking for properties that have a tag `android:"path"`, and automatically add a SourceDepTag dependency on any module references (":module-name") found. Uses a cache to store the mapping of property struct type to locations of properties with the tag. Test: android/path_properties_test.go Change-Id: I38c0497843dde4890e9342c3a6f0b402c0720742 --- android/module.go | 4 +- android/mutator.go | 1 + android/path_properties.go | 123 ++++++++++++++++++++++++++++++++++++++++ android/path_properties_test.go | 120 +++++++++++++++++++++++++++++++++++++++ android/testing.go | 2 + 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 android/path_properties.go create mode 100644 android/path_properties_test.go (limited to 'android') diff --git a/android/module.go b/android/module.go index 218df226..1e6cc838 100644 --- a/android/module.go +++ b/android/module.go @@ -1437,7 +1437,7 @@ func (ctx *androidModuleContext) ExpandSourcesSubDir(srcFiles, excludes []string if m := SrcIsModule(e); m != "" { module := ctx.GetDirectDepWithTag(m, SourceDepTag) if module == nil { - // Error will have been handled by ExtractSourcesDeps + ctx.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m) continue } if srcProducer, ok := module.(SourceFileProducer); ok { @@ -1454,7 +1454,7 @@ func (ctx *androidModuleContext) ExpandSourcesSubDir(srcFiles, excludes []string if m := SrcIsModule(s); m != "" { module := ctx.GetDirectDepWithTag(m, SourceDepTag) if module == nil { - // Error will have been handled by ExtractSourcesDeps + ctx.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m) continue } if srcProducer, ok := module.(SourceFileProducer); ok { diff --git a/android/mutator.go b/android/mutator.go index 509b67fa..cd1a2d54 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -91,6 +91,7 @@ var preDeps = []RegisterMutatorFunc{ } var postDeps = []RegisterMutatorFunc{ + registerPathDepsMutator, RegisterPrebuiltsPostDepsMutators, registerNeverallowMutator, } diff --git a/android/path_properties.go b/android/path_properties.go new file mode 100644 index 00000000..5d897092 --- /dev/null +++ b/android/path_properties.go @@ -0,0 +1,123 @@ +// Copyright 2019 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 android + +import ( + "fmt" + "reflect" + + "github.com/google/blueprint/proptools" +) + +func registerPathDepsMutator(ctx RegisterMutatorsContext) { + ctx.BottomUp("pathdeps", pathDepsMutator).Parallel() +} + +// The pathDepsMutator automatically adds dependencies on any module that is listed with ":module" syntax in a +// property that is tagged with android:"path". +func pathDepsMutator(ctx BottomUpMutatorContext) { + m := ctx.Module().(Module) + if m == nil { + return + } + + props := m.base().customizableProperties + + for _, ps := range props { + pathProperties := pathPropertiesForPropertyStruct(ctx, ps) + pathProperties = FirstUniqueStrings(pathProperties) + + var deps []string + for _, s := range pathProperties { + if m := SrcIsModule(s); m != "" { + deps = append(deps, m) + } + } + + ctx.AddDependency(ctx.Module(), SourceDepTag, deps...) + } +} + +// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with android:"path" to extract +// all their values from a property struct, returning them as a single slice of strings.. +func pathPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}) []string { + v := reflect.ValueOf(ps) + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + panic(fmt.Errorf("type %s is not a pointer to a struct", v.Type())) + } + if v.IsNil() { + return nil + } + v = v.Elem() + + pathPropertyIndexes := pathPropertyIndexesForPropertyStruct(ps) + + var ret []string + + for _, i := range pathPropertyIndexes { + sv := fieldByIndex(v, i) + if !sv.IsValid() { + continue + } + + if sv.Kind() == reflect.Ptr { + if sv.IsNil() { + continue + } + sv = sv.Elem() + } + switch sv.Kind() { + case reflect.String: + ret = append(ret, sv.String()) + case reflect.Slice: + ret = append(ret, sv.Interface().([]string)...) + default: + panic(fmt.Errorf(`field %s in type %s has tag android:"path" but is not a string or slice of strings, it is a %s`, + v.Type().FieldByIndex(i).Name, v.Type(), sv.Type())) + } + } + + return ret +} + +// fieldByIndex is like reflect.Value.FieldByIndex, but returns an invalid reflect.Value when traversing a nil pointer +// to a struct. +func fieldByIndex(v reflect.Value, index []int) reflect.Value { + if len(index) == 1 { + return v.Field(index[0]) + } + for _, x := range index { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + } + v = v.Field(x) + } + return v +} + +var pathPropertyIndexesCache OncePer + +// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in property struct type that +// are tagged with android:"path". Each index is a []int suitable for passing to reflect.Value.FieldByIndex. The value +// is cached in a global cache by type. +func pathPropertyIndexesForPropertyStruct(ps interface{}) [][]int { + key := NewCustomOnceKey(reflect.TypeOf(ps)) + return pathPropertyIndexesCache.Once(key, func() interface{} { + return proptools.PropertyIndexesWithTag(ps, "android", "path") + }).([][]int) +} diff --git a/android/path_properties_test.go b/android/path_properties_test.go new file mode 100644 index 00000000..6471a3cf --- /dev/null +++ b/android/path_properties_test.go @@ -0,0 +1,120 @@ +// Copyright 2019 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 android + +import ( + "io/ioutil" + "os" + "reflect" + "testing" +) + +type pathDepsMutatorTestModule struct { + ModuleBase + props struct { + Foo string `android:"path"` + Bar []string `android:"path"` + Baz *string `android:"path"` + Qux string + } + + sourceDeps []string +} + +func pathDepsMutatorTestModuleFactory() Module { + module := &pathDepsMutatorTestModule{} + module.AddProperties(&module.props) + InitAndroidModule(module) + return module +} + +func (p *pathDepsMutatorTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + ctx.VisitDirectDepsWithTag(SourceDepTag, func(dep Module) { + p.sourceDeps = append(p.sourceDeps, ctx.OtherModuleName(dep)) + }) +} + +func TestPathDepsMutator(t *testing.T) { + tests := []struct { + name string + bp string + deps []string + }{ + { + name: "all", + bp: ` + test { + name: "foo", + foo: ":a", + bar: [":b"], + baz: ":c", + qux: ":d", + }`, + deps: []string{"a", "b", "c"}, + }, + } + + buildDir, err := ioutil.TempDir("", "soong_path_properties_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(buildDir) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := TestConfig(buildDir, nil) + ctx := NewTestContext() + + ctx.RegisterModuleType("test", ModuleFactoryAdaptor(pathDepsMutatorTestModuleFactory)) + ctx.RegisterModuleType("filegroup", ModuleFactoryAdaptor(FileGroupFactory)) + + bp := test.bp + ` + filegroup { + name: "a", + } + + filegroup { + name: "b", + } + + filegroup { + name: "c", + } + + filegroup { + name: "d", + } + ` + + mockFS := map[string][]byte{ + "Android.bp": []byte(bp), + } + + ctx.MockFileSystem(mockFS) + + ctx.Register() + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + m := ctx.ModuleForTests("foo", "").Module().(*pathDepsMutatorTestModule) + + if g, w := m.sourceDeps, test.deps; !reflect.DeepEqual(g, w) { + t.Errorf("want deps %q, got %q", w, g) + } + }) + } +} diff --git a/android/testing.go b/android/testing.go index b4008af3..7f443a39 100644 --- a/android/testing.go +++ b/android/testing.go @@ -37,6 +37,8 @@ func NewTestContext() *TestContext { ctx.SetNameInterface(nameResolver) + ctx.postDeps = append(ctx.postDeps, registerPathDepsMutator) + return ctx } -- cgit v1.2.3