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