platform_build_soong/android/path_properties.go

169 lines
5.3 KiB
Go
Raw Normal View History

// 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"
)
// This file implements support for automatically adding dependencies on any module referenced
// with the ":module" module reference syntax in a property that is annotated with `android:"path"`.
// The dependency is used by android.PathForModuleSrc to convert the module reference into the path
// to the output file of the referenced module.
func registerPathDepsMutator(ctx RegisterMutatorsContext) {
ctx.BottomUp("pathdeps", pathDepsMutator).Parallel()
}
// The pathDepsMutator automatically adds dependencies on any module that is listed with the
// ":module" module reference syntax in a property that is tagged with `android:"path"`.
func pathDepsMutator(ctx BottomUpMutatorContext) {
props := ctx.Module().base().generalProperties
addPathDepsForProps(ctx, props)
}
func addPathDepsForProps(ctx BottomUpMutatorContext, props []interface{}) {
// Iterate through each property struct of the module extracting the contents of all properties
// tagged with `android:"path"`.
var pathProperties []string
for _, ps := range props {
pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ps)...)
}
// Remove duplicates to avoid multiple dependencies.
pathProperties = FirstUniqueStrings(pathProperties)
// Add dependencies to anything that is a module reference.
for _, s := range pathProperties {
if m, t := SrcIsModuleWithTag(s); m != "" {
Support fully qualified names in `android:"path"` properties Previously, a module reference in a path property would be parsed into two parts, the module name and the optional output tag, which defaults to an empty string if not specified. The output tag would be stored in a sourceOrOutputDependencyTag which would then be used, along with the module name to add a dependency on the module. Later, when the paths were processed the same module reference would be parsed into the same two parts again and the module name used to find a matching Module by comparing it against the name returned by either Module.Name(), ctx.OtherModuleName() or ModuleBase.BaseModuleName(). Once the module had been found then if it supported OutputFilesProducer then the tag would be passed to its OutputFiles(tag) method. Otherwise, it would fall back to treating it as SourceFilesProducer. The problem with that is the module name retrieved from the module in some way (either directly or through a context name) could be different to that originally supplied when adding the dependency. e.g. 1. If the original dependency was added onto a source module but there existed a suitable and preferred prebuilt module then the dependency onto the source module would have been replaced by the prebuilt module which has a different name. 2. If the path property included a fully qualified name that included a qualifying path then it would not match the name retrieved from the module which would not include the qualifying path. This change circumvents that whole issue by adding the module name that was originally used to add the dependency into the DependencyTag. Now the DependencyTag uniquely identifies the original module/outputTag pair parsed from the module reference. The pathDepsMutator guarantees that they are unique as it dedups them before adding the dependencies. It is possible that calling ExtractSource(s)Deps() would add some duplicate but if they did they would be identical, i.e. the same sourceOrOutputDependencyTag would be used to add a dependency onto the exact same module. In that case it would not matter which of the dependencies was found as it would still return the same module. Bug: 193228441 Test: m nothing Change-Id: I661514a2984818e5c26577411cede53eb57bcd02
2021-07-09 18:10:35 +02:00
ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(m, t), m)
}
}
}
// 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(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 the property struct is a nil pointer it can't have any paths set in it.
if v.IsNil() {
return nil
}
// v is now the reflect.Value for the concrete property struct.
v = v.Elem()
// Get or create the list of indexes of properties that are tagged with `android:"path"`.
pathPropertyIndexes := pathPropertyIndexesForPropertyStruct(ps)
var ret []string
for _, i := range pathPropertyIndexes {
var values []reflect.Value
fieldsByIndex(v, i, &values)
for _, sv := range values {
if !sv.IsValid() {
// Skip properties inside a nil pointer.
continue
}
// If the field is a non-nil pointer step into it.
if sv.Kind() == reflect.Ptr {
if sv.IsNil() {
continue
}
sv = sv.Elem()
}
// Collect paths from all strings and slices of strings.
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
}
// fieldsByIndex is similar to reflect.Value.FieldByIndex, but is more robust: it doesn't track
// nil pointers and it returns multiple values when there's slice of struct.
func fieldsByIndex(v reflect.Value, index []int, values *[]reflect.Value) {
// leaf case
if len(index) == 1 {
if isSliceOfStruct(v) {
for i := 0; i < v.Len(); i++ {
*values = append(*values, v.Index(i).Field(index[0]))
}
} else {
// Dereference it if it's a pointer.
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return
}
v = v.Elem()
}
*values = append(*values, v.Field(index[0]))
}
return
}
// recursion
if v.Kind() == reflect.Ptr {
// don't track nil pointer
if v.IsNil() {
return
}
v = v.Elem()
} else if isSliceOfStruct(v) {
// do the recursion for all elements
for i := 0; i < v.Len(); i++ {
fieldsByIndex(v.Index(i).Field(index[0]), index[1:], values)
}
return
}
fieldsByIndex(v.Field(index[0]), index[1:], values)
return
}
func isSliceOfStruct(v reflect.Value) bool {
return v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Struct
}
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)
}