blob: 0494a28eeb064d7ddddf9198d266402b29b28edd [file] [log] [blame]
Paul Duffin18122942021-08-24 19:01:25 +01001// 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"
Paul Duffin0c3acbf2021-08-24 19:01:25 +010019 "reflect"
Paul Duffin18122942021-08-24 19:01:25 +010020 "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//
Paul Duffinbe750592022-07-15 13:12:35 +000027// The name of the release, is the same as the code for the dessert release, e.g. S, Tiramisu, etc.
Paul Duffin18122942021-08-24 19:01:25 +010028type buildRelease struct {
Paul Duffinbe750592022-07-15 13:12:35 +000029 // The name of the release, e.g. S, Tiramisu, etc.
Paul Duffin18122942021-08-24 19:01:25 +010030 name string
31
32 // The index of this structure within the buildReleases list.
33 ordinal int
34}
35
Paul Duffinbe750592022-07-15 13:12:35 +000036func (br *buildRelease) EarlierThan(other *buildRelease) bool {
37 return br.ordinal < other.ordinal
38}
39
Paul Duffin18122942021-08-24 19:01:25 +010040// String returns the name of the build release.
Paul Duffinbe750592022-07-15 13:12:35 +000041func (br *buildRelease) String() string {
42 return br.name
Paul Duffin18122942021-08-24 19:01:25 +010043}
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")
Paul Duffine7babdb2022-02-10 13:06:54 +000092 buildReleaseT = initBuildRelease("Tiramisu")
Paul Duffin18122942021-08-24 19:01:25 +010093)
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}
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100166
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
Paul Duffin545c5922022-01-27 16:51:51 +0000237 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)
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100258 }
259
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100260 property := prunerProperty{
261 name,
262 fieldPruner,
263 }
264 p.properties = append(p.properties, property)
Paul Duffin545c5922022-01-27 16:51:51 +0000265 } 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)
Paul Duffin106a3a42022-01-27 16:39:06 +0000276
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 }
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100321 }
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100322 }
323 }
324}
325
Paul Duffin545c5922022-01-27 16:51:51 +0000326// pruneProperties will prune (set to zero value) any properties in the struct referenced by the
327// supplied struct pointer.
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100328//
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{}) {
Paul Duffin545c5922022-01-27 16:51:51 +0000332
333 defer func() {
334 if r := recover(); r != nil {
335 panic(fmt.Errorf("%s\n\tof container %#v", r, propertiesStruct))
336 }
337 }()
338
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100339 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()
Paul Duffin106a3a42022-01-27 16:39:06 +0000356 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 {
Paul Duffin0c3acbf2021-08-24 19:01:25 +0100363 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}