• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package sdk
16
17import (
18	"fmt"
19	"reflect"
20	"strings"
21)
22
23// Supports customizing sdk snapshot output based on target build release.
24
25// buildRelease represents the version of a build system used to create a specific release.
26//
27// The name of the release, is the same as the code for the dessert release, e.g. S, Tiramisu, etc.
28type buildRelease struct {
29	// The name of the release, e.g. S, Tiramisu, etc.
30	name string
31
32	// The index of this structure within the buildReleases list.
33	ordinal int
34}
35
36func (br *buildRelease) EarlierThan(other *buildRelease) bool {
37	return br.ordinal < other.ordinal
38}
39
40// String returns the name of the build release.
41func (br *buildRelease) String() string {
42	return br.name
43}
44
45// buildReleaseSet represents a set of buildRelease objects.
46type buildReleaseSet struct {
47	// Set of *buildRelease represented as a map from *buildRelease to struct{}.
48	contents map[*buildRelease]struct{}
49}
50
51// addItem adds a build release to the set.
52func (s *buildReleaseSet) addItem(release *buildRelease) {
53	s.contents[release] = struct{}{}
54}
55
56// addRange adds all the build releases from start (inclusive) to end (inclusive).
57func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) {
58	for i := start.ordinal; i <= end.ordinal; i += 1 {
59		s.addItem(buildReleases[i])
60	}
61}
62
63// contains returns true if the set contains the specified build release.
64func (s *buildReleaseSet) contains(release *buildRelease) bool {
65	_, ok := s.contents[release]
66	return ok
67}
68
69// String returns a string representation of the set, sorted from earliest to latest release.
70func (s *buildReleaseSet) String() string {
71	list := []string{}
72	for _, release := range buildReleases {
73		if _, ok := s.contents[release]; ok {
74			list = append(list, release.name)
75		}
76	}
77	return fmt.Sprintf("[%s]", strings.Join(list, ","))
78}
79
80var (
81	// nameToBuildRelease contains a map from name to build release.
82	nameToBuildRelease = map[string]*buildRelease{}
83
84	// buildReleases lists all the available build releases.
85	buildReleases = []*buildRelease{}
86
87	// allBuildReleaseSet is the set of all build releases.
88	allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
89
90	// Add the build releases from oldest to newest.
91	buildReleaseS = initBuildRelease("S")
92	buildReleaseT = initBuildRelease("Tiramisu")
93)
94
95// initBuildRelease creates a new build release with the specified name.
96func initBuildRelease(name string) *buildRelease {
97	ordinal := len(nameToBuildRelease)
98	release := &buildRelease{name: name, ordinal: ordinal}
99	nameToBuildRelease[name] = release
100	buildReleases = append(buildReleases, release)
101	allBuildReleaseSet.addItem(release)
102	return release
103}
104
105// latestBuildRelease returns the latest build release, i.e. the last one added.
106func latestBuildRelease() *buildRelease {
107	return buildReleases[len(buildReleases)-1]
108}
109
110// nameToRelease maps from build release name to the corresponding build release (if it exists) or
111// the error if it does not.
112func nameToRelease(name string) (*buildRelease, error) {
113	if r, ok := nameToBuildRelease[name]; ok {
114		return r, nil
115	}
116
117	return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet)
118}
119
120// parseBuildReleaseSet parses a build release set string specification into a build release set.
121//
122// The specification consists of one of the following:
123// * a single build release name, e.g. S, T, etc.
124// * a closed range (inclusive to inclusive), e.g. S-T
125// * an open range, e.g. T+.
126//
127// This returns the set if the specification was valid or an error.
128func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) {
129	set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
130
131	if strings.HasSuffix(specification, "+") {
132		rangeStart := strings.TrimSuffix(specification, "+")
133		start, err := nameToRelease(rangeStart)
134		if err != nil {
135			return nil, err
136		}
137		end := latestBuildRelease()
138		set.addRange(start, end)
139	} else if strings.Contains(specification, "-") {
140		limits := strings.SplitN(specification, "-", 2)
141		start, err := nameToRelease(limits[0])
142		if err != nil {
143			return nil, err
144		}
145
146		end, err := nameToRelease(limits[1])
147		if err != nil {
148			return nil, err
149		}
150
151		if start.ordinal > end.ordinal {
152			return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name)
153		}
154
155		set.addRange(start, end)
156	} else {
157		release, err := nameToRelease(specification)
158		if err != nil {
159			return nil, err
160		}
161		set.addItem(release)
162	}
163
164	return set, nil
165}
166
167// Given a set of properties (struct value), set the value of a field within that struct (or one of
168// its embedded structs) to its zero value.
169type fieldPrunerFunc func(structValue reflect.Value)
170
171// A property that can be cleared by a propertyPruner.
172type prunerProperty struct {
173	// The name of the field for this property. It is a "."-separated path for fields in non-anonymous
174	// sub-structs.
175	name string
176
177	// Sets the associated field to its zero value.
178	prunerFunc fieldPrunerFunc
179}
180
181// propertyPruner provides support for pruning (i.e. setting to their zero value) properties from
182// a properties structure.
183type propertyPruner struct {
184	// The properties that the pruner will clear.
185	properties []prunerProperty
186}
187
188// gatherFields recursively processes the supplied structure and a nested structures, selecting the
189// fields that require pruning and populates the propertyPruner.properties with the information
190// needed to prune those fields.
191//
192// containingStructAccessor is a func that if given an object will return a field whose value is
193// of the supplied structType. It is nil on initial entry to this method but when this method is
194// called recursively on a field that is a nested structure containingStructAccessor is set to a
195// func that provides access to the field's value.
196//
197// namePrefix is the prefix to the fields that are being visited. It is "" on initial entry to this
198// method but when this method is called recursively on a field that is a nested structure
199// namePrefix is the result of appending the field name (plus a ".") to the previous name prefix.
200// Unless the field is anonymous in which case it is passed through unchanged.
201//
202// selector is a func that will select whether the supplied field requires pruning or not. If it
203// returns true then the field will be added to those to be pruned, otherwise it will not.
204func (p *propertyPruner) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string, selector fieldSelectorFunc) {
205	for f := 0; f < structType.NumField(); f++ {
206		field := structType.Field(f)
207		if field.PkgPath != "" {
208			// Ignore unexported fields.
209			continue
210		}
211
212		// Save a copy of the field index for use in the function.
213		fieldIndex := f
214
215		name := namePrefix + field.Name
216
217		fieldGetter := func(container reflect.Value) reflect.Value {
218			if containingStructAccessor != nil {
219				// This is an embedded structure so first access the field for the embedded
220				// structure.
221				container = containingStructAccessor(container)
222			}
223
224			// Skip through interface and pointer values to find the structure.
225			container = getStructValue(container)
226
227			defer func() {
228				if r := recover(); r != nil {
229					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
230				}
231			}()
232
233			// Return the field.
234			return container.Field(fieldIndex)
235		}
236
237		fieldType := field.Type
238		if selector(name, field) {
239			zeroValue := reflect.Zero(fieldType)
240			fieldPruner := func(container reflect.Value) {
241				if containingStructAccessor != nil {
242					// This is an embedded structure so first access the field for the embedded
243					// structure.
244					container = containingStructAccessor(container)
245				}
246
247				// Skip through interface and pointer values to find the structure.
248				container = getStructValue(container)
249
250				defer func() {
251					if r := recover(); r != nil {
252						panic(fmt.Errorf("%s\n\tfor field (index %d, name %s)", r, fieldIndex, name))
253					}
254				}()
255
256				// Set the field.
257				container.Field(fieldIndex).Set(zeroValue)
258			}
259
260			property := prunerProperty{
261				name,
262				fieldPruner,
263			}
264			p.properties = append(p.properties, property)
265		} else {
266			switch fieldType.Kind() {
267			case reflect.Struct:
268				// Gather fields from the nested or embedded structure.
269				var subNamePrefix string
270				if field.Anonymous {
271					subNamePrefix = namePrefix
272				} else {
273					subNamePrefix = name + "."
274				}
275				p.gatherFields(fieldType, fieldGetter, subNamePrefix, selector)
276
277			case reflect.Map:
278				// Get the type of the values stored in the map.
279				valueType := fieldType.Elem()
280				// Skip over * types.
281				if valueType.Kind() == reflect.Ptr {
282					valueType = valueType.Elem()
283				}
284				if valueType.Kind() == reflect.Struct {
285					// If this is not referenced by a pointer then it is an error as it is impossible to
286					// modify a struct that is stored directly as a value in a map.
287					if fieldType.Elem().Kind() != reflect.Ptr {
288						panic(fmt.Errorf("Cannot prune struct %s stored by value in map %s, map values must"+
289							" be pointers to structs",
290							fieldType.Elem(), name))
291					}
292
293					// Create a new pruner for the values of the map.
294					valuePruner := newPropertyPrunerForStructType(valueType, selector)
295
296					// Create a new fieldPruner that will iterate over all the items in the map and call the
297					// pruner on them.
298					fieldPruner := func(container reflect.Value) {
299						mapValue := fieldGetter(container)
300
301						for _, keyValue := range mapValue.MapKeys() {
302							itemValue := mapValue.MapIndex(keyValue)
303
304							defer func() {
305								if r := recover(); r != nil {
306									panic(fmt.Errorf("%s\n\tfor key %q", r, keyValue))
307								}
308							}()
309
310							valuePruner.pruneProperties(itemValue.Interface())
311						}
312					}
313
314					// Add the map field pruner to the list of property pruners.
315					property := prunerProperty{
316						name + "[*]",
317						fieldPruner,
318					}
319					p.properties = append(p.properties, property)
320				}
321			}
322		}
323	}
324}
325
326// pruneProperties will prune (set to zero value) any properties in the struct referenced by the
327// supplied struct pointer.
328//
329// The struct must be of the same type as was originally passed to newPropertyPruner to create this
330// propertyPruner.
331func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) {
332
333	defer func() {
334		if r := recover(); r != nil {
335			panic(fmt.Errorf("%s\n\tof container %#v", r, propertiesStruct))
336		}
337	}()
338
339	structValue := reflect.ValueOf(propertiesStruct)
340	for _, property := range p.properties {
341		property.prunerFunc(structValue)
342	}
343}
344
345// fieldSelectorFunc is called to select whether a specific field should be pruned or not.
346// name is the name of the field, including any prefixes from containing str
347type fieldSelectorFunc func(name string, field reflect.StructField) bool
348
349// newPropertyPruner creates a new property pruner for the structure type for the supplied
350// properties struct.
351//
352// The returned pruner can be used on any properties structure of the same type as the supplied set
353// of properties.
354func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner {
355	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
356	return newPropertyPrunerForStructType(structType, selector)
357}
358
359// newPropertyPruner creates a new property pruner for the supplied properties struct type.
360//
361// The returned pruner can be used on any properties structure of the supplied type.
362func newPropertyPrunerForStructType(structType reflect.Type, selector fieldSelectorFunc) *propertyPruner {
363	pruner := &propertyPruner{}
364	pruner.gatherFields(structType, nil, "", selector)
365	return pruner
366}
367
368// newPropertyPrunerByBuildRelease creates a property pruner that will clear any properties in the
369// structure which are not supported by the specified target build release.
370//
371// A property is pruned if its field has a tag of the form:
372//     `supported_build_releases:"<build-release-set>"`
373// and the resulting build release set does not contain the target build release. Properties that
374// have no such tag are assumed to be supported by all releases.
375func newPropertyPrunerByBuildRelease(propertiesStruct interface{}, targetBuildRelease *buildRelease) *propertyPruner {
376	return newPropertyPruner(propertiesStruct, func(name string, field reflect.StructField) bool {
377		if supportedBuildReleases, ok := field.Tag.Lookup("supported_build_releases"); ok {
378			set, err := parseBuildReleaseSet(supportedBuildReleases)
379			if err != nil {
380				panic(fmt.Errorf("invalid `supported_build_releases` tag on %s of %T: %s", name, propertiesStruct, err))
381			}
382
383			// If the field does not support tha target release then prune it.
384			return !set.contains(targetBuildRelease)
385
386		} else {
387			// Any untagged fields are assumed to be supported by all build releases so should never be
388			// pruned.
389			return false
390		}
391	})
392}
393