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