xref: /aosp_15_r20/build/blueprint/proptools/repack.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2024 Google Inc. All rights reserved.
2*1fa6dee9SAndroid Build Coastguard Worker//
3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*1fa6dee9SAndroid Build Coastguard Worker//
7*1fa6dee9SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*1fa6dee9SAndroid Build Coastguard Worker//
9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License.
14*1fa6dee9SAndroid Build Coastguard Worker
15*1fa6dee9SAndroid Build Coastguard Workerpackage proptools
16*1fa6dee9SAndroid Build Coastguard Worker
17*1fa6dee9SAndroid Build Coastguard Workerimport (
18*1fa6dee9SAndroid Build Coastguard Worker	"fmt"
19*1fa6dee9SAndroid Build Coastguard Worker	"reflect"
20*1fa6dee9SAndroid Build Coastguard Worker	"slices"
21*1fa6dee9SAndroid Build Coastguard Worker
22*1fa6dee9SAndroid Build Coastguard Worker	"github.com/google/blueprint/parser"
23*1fa6dee9SAndroid Build Coastguard Worker)
24*1fa6dee9SAndroid Build Coastguard Worker
25*1fa6dee9SAndroid Build Coastguard Workerfunc RepackProperties(props []interface{}) (*parser.Map, error) {
26*1fa6dee9SAndroid Build Coastguard Worker
27*1fa6dee9SAndroid Build Coastguard Worker	var dereferencedProps []reflect.Value
28*1fa6dee9SAndroid Build Coastguard Worker	for _, rawProp := range props {
29*1fa6dee9SAndroid Build Coastguard Worker		propStruct := reflect.ValueOf(rawProp)
30*1fa6dee9SAndroid Build Coastguard Worker		if !isStructPtr(propStruct.Type()) {
31*1fa6dee9SAndroid Build Coastguard Worker			return nil, fmt.Errorf("properties must be *struct, got %s",
32*1fa6dee9SAndroid Build Coastguard Worker				propStruct.Type())
33*1fa6dee9SAndroid Build Coastguard Worker		}
34*1fa6dee9SAndroid Build Coastguard Worker		propStruct = propStruct.Elem()
35*1fa6dee9SAndroid Build Coastguard Worker		dereferencedProps = append(dereferencedProps, propStruct)
36*1fa6dee9SAndroid Build Coastguard Worker	}
37*1fa6dee9SAndroid Build Coastguard Worker
38*1fa6dee9SAndroid Build Coastguard Worker	return repackStructs(dereferencedProps)
39*1fa6dee9SAndroid Build Coastguard Worker}
40*1fa6dee9SAndroid Build Coastguard Worker
41*1fa6dee9SAndroid Build Coastguard Workerfunc repackStructs(props []reflect.Value) (*parser.Map, error) {
42*1fa6dee9SAndroid Build Coastguard Worker	var allFieldNames []string
43*1fa6dee9SAndroid Build Coastguard Worker	for _, prop := range props {
44*1fa6dee9SAndroid Build Coastguard Worker		propType := prop.Type()
45*1fa6dee9SAndroid Build Coastguard Worker		for i := 0; i < propType.NumField(); i++ {
46*1fa6dee9SAndroid Build Coastguard Worker			field := propType.Field(i)
47*1fa6dee9SAndroid Build Coastguard Worker			if !slices.Contains(allFieldNames, field.Name) {
48*1fa6dee9SAndroid Build Coastguard Worker				allFieldNames = append(allFieldNames, field.Name)
49*1fa6dee9SAndroid Build Coastguard Worker			}
50*1fa6dee9SAndroid Build Coastguard Worker		}
51*1fa6dee9SAndroid Build Coastguard Worker	}
52*1fa6dee9SAndroid Build Coastguard Worker
53*1fa6dee9SAndroid Build Coastguard Worker	result := &parser.Map{}
54*1fa6dee9SAndroid Build Coastguard Worker
55*1fa6dee9SAndroid Build Coastguard Worker	for _, fieldName := range allFieldNames {
56*1fa6dee9SAndroid Build Coastguard Worker		var fields []reflect.Value
57*1fa6dee9SAndroid Build Coastguard Worker		for _, prop := range props {
58*1fa6dee9SAndroid Build Coastguard Worker			field := prop.FieldByName(fieldName)
59*1fa6dee9SAndroid Build Coastguard Worker			if field.IsValid() {
60*1fa6dee9SAndroid Build Coastguard Worker				fields = append(fields, field)
61*1fa6dee9SAndroid Build Coastguard Worker			}
62*1fa6dee9SAndroid Build Coastguard Worker		}
63*1fa6dee9SAndroid Build Coastguard Worker		if err := assertFieldsEquivalent(fields); err != nil {
64*1fa6dee9SAndroid Build Coastguard Worker			return nil, err
65*1fa6dee9SAndroid Build Coastguard Worker		}
66*1fa6dee9SAndroid Build Coastguard Worker
67*1fa6dee9SAndroid Build Coastguard Worker		var expr parser.Expression
68*1fa6dee9SAndroid Build Coastguard Worker		var field reflect.Value
69*1fa6dee9SAndroid Build Coastguard Worker		for _, f := range fields {
70*1fa6dee9SAndroid Build Coastguard Worker			if !isPropEmpty(f) {
71*1fa6dee9SAndroid Build Coastguard Worker				field = f
72*1fa6dee9SAndroid Build Coastguard Worker				break
73*1fa6dee9SAndroid Build Coastguard Worker			}
74*1fa6dee9SAndroid Build Coastguard Worker		}
75*1fa6dee9SAndroid Build Coastguard Worker		if !field.IsValid() {
76*1fa6dee9SAndroid Build Coastguard Worker			continue
77*1fa6dee9SAndroid Build Coastguard Worker		}
78*1fa6dee9SAndroid Build Coastguard Worker		if isStruct(field.Type()) && !isConfigurable(field.Type()) {
79*1fa6dee9SAndroid Build Coastguard Worker			x, err := repackStructs(fields)
80*1fa6dee9SAndroid Build Coastguard Worker			if err != nil {
81*1fa6dee9SAndroid Build Coastguard Worker				return nil, err
82*1fa6dee9SAndroid Build Coastguard Worker			}
83*1fa6dee9SAndroid Build Coastguard Worker			if x != nil {
84*1fa6dee9SAndroid Build Coastguard Worker				expr = x
85*1fa6dee9SAndroid Build Coastguard Worker			}
86*1fa6dee9SAndroid Build Coastguard Worker		} else {
87*1fa6dee9SAndroid Build Coastguard Worker			x, err := fieldToExpr(field)
88*1fa6dee9SAndroid Build Coastguard Worker			if err != nil {
89*1fa6dee9SAndroid Build Coastguard Worker				return nil, err
90*1fa6dee9SAndroid Build Coastguard Worker			}
91*1fa6dee9SAndroid Build Coastguard Worker			if x != nil {
92*1fa6dee9SAndroid Build Coastguard Worker				expr = *x
93*1fa6dee9SAndroid Build Coastguard Worker			}
94*1fa6dee9SAndroid Build Coastguard Worker		}
95*1fa6dee9SAndroid Build Coastguard Worker
96*1fa6dee9SAndroid Build Coastguard Worker		if expr != nil {
97*1fa6dee9SAndroid Build Coastguard Worker			result.Properties = append(result.Properties, &parser.Property{
98*1fa6dee9SAndroid Build Coastguard Worker				Name:  PropertyNameForField(fieldName),
99*1fa6dee9SAndroid Build Coastguard Worker				Value: expr,
100*1fa6dee9SAndroid Build Coastguard Worker			})
101*1fa6dee9SAndroid Build Coastguard Worker		}
102*1fa6dee9SAndroid Build Coastguard Worker	}
103*1fa6dee9SAndroid Build Coastguard Worker
104*1fa6dee9SAndroid Build Coastguard Worker	return result, nil
105*1fa6dee9SAndroid Build Coastguard Worker}
106*1fa6dee9SAndroid Build Coastguard Worker
107*1fa6dee9SAndroid Build Coastguard Workerfunc fieldToExpr(field reflect.Value) (*parser.Expression, error) {
108*1fa6dee9SAndroid Build Coastguard Worker	if IsConfigurable(field.Type()) {
109*1fa6dee9SAndroid Build Coastguard Worker		return field.Interface().(configurableReflection).toExpression()
110*1fa6dee9SAndroid Build Coastguard Worker	}
111*1fa6dee9SAndroid Build Coastguard Worker	if field.Kind() == reflect.Pointer {
112*1fa6dee9SAndroid Build Coastguard Worker		if field.IsNil() {
113*1fa6dee9SAndroid Build Coastguard Worker			return nil, nil
114*1fa6dee9SAndroid Build Coastguard Worker		}
115*1fa6dee9SAndroid Build Coastguard Worker		field = field.Elem()
116*1fa6dee9SAndroid Build Coastguard Worker	}
117*1fa6dee9SAndroid Build Coastguard Worker	switch field.Kind() {
118*1fa6dee9SAndroid Build Coastguard Worker	case reflect.String:
119*1fa6dee9SAndroid Build Coastguard Worker		var result parser.Expression = &parser.String{Value: field.String()}
120*1fa6dee9SAndroid Build Coastguard Worker		return &result, nil
121*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Bool:
122*1fa6dee9SAndroid Build Coastguard Worker		var result parser.Expression = &parser.Bool{Value: field.Bool()}
123*1fa6dee9SAndroid Build Coastguard Worker		return &result, nil
124*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Int, reflect.Int64:
125*1fa6dee9SAndroid Build Coastguard Worker		var result parser.Expression = &parser.Int64{Value: field.Int()}
126*1fa6dee9SAndroid Build Coastguard Worker		return &result, nil
127*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Slice:
128*1fa6dee9SAndroid Build Coastguard Worker		var contents []parser.Expression
129*1fa6dee9SAndroid Build Coastguard Worker		for i := 0; i < field.Len(); i++ {
130*1fa6dee9SAndroid Build Coastguard Worker			inner, err := fieldToExpr(field.Index(i))
131*1fa6dee9SAndroid Build Coastguard Worker			if err != nil {
132*1fa6dee9SAndroid Build Coastguard Worker				return nil, err
133*1fa6dee9SAndroid Build Coastguard Worker			}
134*1fa6dee9SAndroid Build Coastguard Worker			contents = append(contents, *inner)
135*1fa6dee9SAndroid Build Coastguard Worker		}
136*1fa6dee9SAndroid Build Coastguard Worker		var result parser.Expression = &parser.List{Values: contents}
137*1fa6dee9SAndroid Build Coastguard Worker		return &result, nil
138*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Struct:
139*1fa6dee9SAndroid Build Coastguard Worker		var properties []*parser.Property
140*1fa6dee9SAndroid Build Coastguard Worker		typ := field.Type()
141*1fa6dee9SAndroid Build Coastguard Worker		for i := 0; i < typ.NumField(); i++ {
142*1fa6dee9SAndroid Build Coastguard Worker			inner, err := fieldToExpr(field.Field(i))
143*1fa6dee9SAndroid Build Coastguard Worker			if err != nil {
144*1fa6dee9SAndroid Build Coastguard Worker				return nil, err
145*1fa6dee9SAndroid Build Coastguard Worker			}
146*1fa6dee9SAndroid Build Coastguard Worker			properties = append(properties, &parser.Property{
147*1fa6dee9SAndroid Build Coastguard Worker				Name:  typ.Field(i).Name,
148*1fa6dee9SAndroid Build Coastguard Worker				Value: *inner,
149*1fa6dee9SAndroid Build Coastguard Worker			})
150*1fa6dee9SAndroid Build Coastguard Worker		}
151*1fa6dee9SAndroid Build Coastguard Worker		var result parser.Expression = &parser.Map{Properties: properties}
152*1fa6dee9SAndroid Build Coastguard Worker		return &result, nil
153*1fa6dee9SAndroid Build Coastguard Worker	default:
154*1fa6dee9SAndroid Build Coastguard Worker		return nil, fmt.Errorf("Unhandled type: %s", field.Kind().String())
155*1fa6dee9SAndroid Build Coastguard Worker	}
156*1fa6dee9SAndroid Build Coastguard Worker}
157*1fa6dee9SAndroid Build Coastguard Worker
158*1fa6dee9SAndroid Build Coastguard Workerfunc isPropEmpty(value reflect.Value) bool {
159*1fa6dee9SAndroid Build Coastguard Worker	switch value.Kind() {
160*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Pointer:
161*1fa6dee9SAndroid Build Coastguard Worker		if value.IsNil() {
162*1fa6dee9SAndroid Build Coastguard Worker			return true
163*1fa6dee9SAndroid Build Coastguard Worker		}
164*1fa6dee9SAndroid Build Coastguard Worker		return isPropEmpty(value.Elem())
165*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Struct:
166*1fa6dee9SAndroid Build Coastguard Worker		if isConfigurable(value.Type()) {
167*1fa6dee9SAndroid Build Coastguard Worker			return value.Interface().(configurableReflection).isEmpty()
168*1fa6dee9SAndroid Build Coastguard Worker		}
169*1fa6dee9SAndroid Build Coastguard Worker		for i := 0; i < value.NumField(); i++ {
170*1fa6dee9SAndroid Build Coastguard Worker			if !isPropEmpty(value.Field(i)) {
171*1fa6dee9SAndroid Build Coastguard Worker				return false
172*1fa6dee9SAndroid Build Coastguard Worker			}
173*1fa6dee9SAndroid Build Coastguard Worker		}
174*1fa6dee9SAndroid Build Coastguard Worker		return true
175*1fa6dee9SAndroid Build Coastguard Worker	default:
176*1fa6dee9SAndroid Build Coastguard Worker		return false
177*1fa6dee9SAndroid Build Coastguard Worker	}
178*1fa6dee9SAndroid Build Coastguard Worker}
179*1fa6dee9SAndroid Build Coastguard Worker
180*1fa6dee9SAndroid Build Coastguard Workerfunc assertFieldsEquivalent(fields []reflect.Value) error {
181*1fa6dee9SAndroid Build Coastguard Worker	var firstNonEmpty reflect.Value
182*1fa6dee9SAndroid Build Coastguard Worker	var firstIndex int
183*1fa6dee9SAndroid Build Coastguard Worker	for i, f := range fields {
184*1fa6dee9SAndroid Build Coastguard Worker		if !isPropEmpty(f) {
185*1fa6dee9SAndroid Build Coastguard Worker			firstNonEmpty = f
186*1fa6dee9SAndroid Build Coastguard Worker			firstIndex = i
187*1fa6dee9SAndroid Build Coastguard Worker			break
188*1fa6dee9SAndroid Build Coastguard Worker		}
189*1fa6dee9SAndroid Build Coastguard Worker	}
190*1fa6dee9SAndroid Build Coastguard Worker	if !firstNonEmpty.IsValid() {
191*1fa6dee9SAndroid Build Coastguard Worker		return nil
192*1fa6dee9SAndroid Build Coastguard Worker	}
193*1fa6dee9SAndroid Build Coastguard Worker	for i, f := range fields {
194*1fa6dee9SAndroid Build Coastguard Worker		if i != firstIndex && !isPropEmpty(f) {
195*1fa6dee9SAndroid Build Coastguard Worker			if err := assertTwoNonEmptyFieldsEquivalent(firstNonEmpty, f); err != nil {
196*1fa6dee9SAndroid Build Coastguard Worker				return err
197*1fa6dee9SAndroid Build Coastguard Worker			}
198*1fa6dee9SAndroid Build Coastguard Worker		}
199*1fa6dee9SAndroid Build Coastguard Worker	}
200*1fa6dee9SAndroid Build Coastguard Worker	return nil
201*1fa6dee9SAndroid Build Coastguard Worker}
202*1fa6dee9SAndroid Build Coastguard Worker
203*1fa6dee9SAndroid Build Coastguard Workerfunc assertTwoNonEmptyFieldsEquivalent(a, b reflect.Value) error {
204*1fa6dee9SAndroid Build Coastguard Worker	aType := a.Type()
205*1fa6dee9SAndroid Build Coastguard Worker	bType := b.Type()
206*1fa6dee9SAndroid Build Coastguard Worker
207*1fa6dee9SAndroid Build Coastguard Worker	if aType != bType {
208*1fa6dee9SAndroid Build Coastguard Worker		return fmt.Errorf("fields must have the same type")
209*1fa6dee9SAndroid Build Coastguard Worker	}
210*1fa6dee9SAndroid Build Coastguard Worker
211*1fa6dee9SAndroid Build Coastguard Worker	switch aType.Kind() {
212*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Pointer:
213*1fa6dee9SAndroid Build Coastguard Worker		return assertTwoNonEmptyFieldsEquivalent(a.Elem(), b.Elem())
214*1fa6dee9SAndroid Build Coastguard Worker	case reflect.String:
215*1fa6dee9SAndroid Build Coastguard Worker		if a.String() != b.String() {
216*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("Conflicting fields in property structs had values %q and %q", a.String(), b.String())
217*1fa6dee9SAndroid Build Coastguard Worker		}
218*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Bool:
219*1fa6dee9SAndroid Build Coastguard Worker		if a.Bool() != b.Bool() {
220*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("Conflicting fields in property structs had values %t and %t", a.Bool(), b.Bool())
221*1fa6dee9SAndroid Build Coastguard Worker		}
222*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Slice:
223*1fa6dee9SAndroid Build Coastguard Worker		if a.Len() != b.Len() {
224*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("Conflicting fields in property structs had lengths %d and %d", a.Len(), b.Len())
225*1fa6dee9SAndroid Build Coastguard Worker		}
226*1fa6dee9SAndroid Build Coastguard Worker		for i := 0; i < a.Len(); i++ {
227*1fa6dee9SAndroid Build Coastguard Worker			if err := assertTwoNonEmptyFieldsEquivalent(a.Index(i), b.Index(i)); err != nil {
228*1fa6dee9SAndroid Build Coastguard Worker				return err
229*1fa6dee9SAndroid Build Coastguard Worker			}
230*1fa6dee9SAndroid Build Coastguard Worker		}
231*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Int:
232*1fa6dee9SAndroid Build Coastguard Worker		if a.Int() != b.Int() {
233*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("Conflicting fields in property structs had values %d and %d", a.Int(), b.Int())
234*1fa6dee9SAndroid Build Coastguard Worker		}
235*1fa6dee9SAndroid Build Coastguard Worker	case reflect.Struct:
236*1fa6dee9SAndroid Build Coastguard Worker		if isConfigurable(a.Type()) {
237*1fa6dee9SAndroid Build Coastguard Worker			// We could properly check that two configurables are equivalent, but that's a lot more
238*1fa6dee9SAndroid Build Coastguard Worker			// work for a case that I don't think should show up in practice.
239*1fa6dee9SAndroid Build Coastguard Worker			return fmt.Errorf("Cannot handle two property structs with nonempty configurable properties")
240*1fa6dee9SAndroid Build Coastguard Worker		}
241*1fa6dee9SAndroid Build Coastguard Worker		// We don't care about checking if structs are equivalent, we'll check their individual
242*1fa6dee9SAndroid Build Coastguard Worker		// fields when we recurse down.
243*1fa6dee9SAndroid Build Coastguard Worker	default:
244*1fa6dee9SAndroid Build Coastguard Worker		return fmt.Errorf("Unhandled kind: %s", aType.Kind().String())
245*1fa6dee9SAndroid Build Coastguard Worker	}
246*1fa6dee9SAndroid Build Coastguard Worker
247*1fa6dee9SAndroid Build Coastguard Worker	return nil
248*1fa6dee9SAndroid Build Coastguard Worker}
249