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 bpmodify 16*1fa6dee9SAndroid Build Coastguard Worker 17*1fa6dee9SAndroid Build Coastguard Workerimport ( 18*1fa6dee9SAndroid Build Coastguard Worker "bytes" 19*1fa6dee9SAndroid Build Coastguard Worker "errors" 20*1fa6dee9SAndroid Build Coastguard Worker "fmt" 21*1fa6dee9SAndroid Build Coastguard Worker "slices" 22*1fa6dee9SAndroid Build Coastguard Worker "strings" 23*1fa6dee9SAndroid Build Coastguard Worker 24*1fa6dee9SAndroid Build Coastguard Worker "github.com/google/blueprint/parser" 25*1fa6dee9SAndroid Build Coastguard Worker) 26*1fa6dee9SAndroid Build Coastguard Worker 27*1fa6dee9SAndroid Build Coastguard Worker// NewBlueprint returns a Blueprint for the given file contents that allows making modifications. 28*1fa6dee9SAndroid Build Coastguard Workerfunc NewBlueprint(filename string, data []byte) (*Blueprint, error) { 29*1fa6dee9SAndroid Build Coastguard Worker r := bytes.NewReader(data) 30*1fa6dee9SAndroid Build Coastguard Worker file, errs := parser.Parse(filename, r) 31*1fa6dee9SAndroid Build Coastguard Worker if len(errs) > 0 { 32*1fa6dee9SAndroid Build Coastguard Worker return nil, errors.Join(errs...) 33*1fa6dee9SAndroid Build Coastguard Worker } 34*1fa6dee9SAndroid Build Coastguard Worker 35*1fa6dee9SAndroid Build Coastguard Worker return &Blueprint{ 36*1fa6dee9SAndroid Build Coastguard Worker data: data, 37*1fa6dee9SAndroid Build Coastguard Worker bpFile: file, 38*1fa6dee9SAndroid Build Coastguard Worker }, nil 39*1fa6dee9SAndroid Build Coastguard Worker} 40*1fa6dee9SAndroid Build Coastguard Worker 41*1fa6dee9SAndroid Build Coastguard Workertype Blueprint struct { 42*1fa6dee9SAndroid Build Coastguard Worker data []byte 43*1fa6dee9SAndroid Build Coastguard Worker bpFile *parser.File 44*1fa6dee9SAndroid Build Coastguard Worker modified bool 45*1fa6dee9SAndroid Build Coastguard Worker} 46*1fa6dee9SAndroid Build Coastguard Worker 47*1fa6dee9SAndroid Build Coastguard Worker// Bytes returns a copy of the current, possibly modified contents of the Blueprint as a byte slice. 48*1fa6dee9SAndroid Build Coastguard Workerfunc (bp *Blueprint) Bytes() ([]byte, error) { 49*1fa6dee9SAndroid Build Coastguard Worker if bp.modified { 50*1fa6dee9SAndroid Build Coastguard Worker data, err := parser.Print(bp.bpFile) 51*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 52*1fa6dee9SAndroid Build Coastguard Worker return nil, err 53*1fa6dee9SAndroid Build Coastguard Worker } 54*1fa6dee9SAndroid Build Coastguard Worker return data, nil 55*1fa6dee9SAndroid Build Coastguard Worker } 56*1fa6dee9SAndroid Build Coastguard Worker return slices.Clone(bp.data), nil 57*1fa6dee9SAndroid Build Coastguard Worker} 58*1fa6dee9SAndroid Build Coastguard Worker 59*1fa6dee9SAndroid Build Coastguard Worker// String returns the current, possibly modified contents of the Blueprint as a string. 60*1fa6dee9SAndroid Build Coastguard Workerfunc (bp *Blueprint) String() string { 61*1fa6dee9SAndroid Build Coastguard Worker data, err := bp.Bytes() 62*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 63*1fa6dee9SAndroid Build Coastguard Worker return err.Error() 64*1fa6dee9SAndroid Build Coastguard Worker } 65*1fa6dee9SAndroid Build Coastguard Worker return string(data) 66*1fa6dee9SAndroid Build Coastguard Worker} 67*1fa6dee9SAndroid Build Coastguard Worker 68*1fa6dee9SAndroid Build Coastguard Worker// Modified returns true if any of the calls on the Blueprint caused the contents to be modified. 69*1fa6dee9SAndroid Build Coastguard Workerfunc (bp *Blueprint) Modified() bool { 70*1fa6dee9SAndroid Build Coastguard Worker return bp.modified 71*1fa6dee9SAndroid Build Coastguard Worker} 72*1fa6dee9SAndroid Build Coastguard Worker 73*1fa6dee9SAndroid Build Coastguard Worker// ModulesByName returns a ModuleSet that contains all modules with the given list of names. 74*1fa6dee9SAndroid Build Coastguard Worker// Requesting a module that does not exist is not an error. 75*1fa6dee9SAndroid Build Coastguard Workerfunc (bp *Blueprint) ModulesByName(names ...string) *ModuleSet { 76*1fa6dee9SAndroid Build Coastguard Worker moduleSet := &ModuleSet{ 77*1fa6dee9SAndroid Build Coastguard Worker bp: bp, 78*1fa6dee9SAndroid Build Coastguard Worker } 79*1fa6dee9SAndroid Build Coastguard Worker for _, def := range bp.bpFile.Defs { 80*1fa6dee9SAndroid Build Coastguard Worker module, ok := def.(*parser.Module) 81*1fa6dee9SAndroid Build Coastguard Worker if !ok { 82*1fa6dee9SAndroid Build Coastguard Worker continue 83*1fa6dee9SAndroid Build Coastguard Worker } 84*1fa6dee9SAndroid Build Coastguard Worker 85*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range module.Properties { 86*1fa6dee9SAndroid Build Coastguard Worker if prop.Name == "name" { 87*1fa6dee9SAndroid Build Coastguard Worker if stringValue, ok := prop.Value.(*parser.String); ok && slices.Contains(names, stringValue.Value) { 88*1fa6dee9SAndroid Build Coastguard Worker moduleSet.modules = append(moduleSet.modules, module) 89*1fa6dee9SAndroid Build Coastguard Worker } 90*1fa6dee9SAndroid Build Coastguard Worker } 91*1fa6dee9SAndroid Build Coastguard Worker } 92*1fa6dee9SAndroid Build Coastguard Worker } 93*1fa6dee9SAndroid Build Coastguard Worker 94*1fa6dee9SAndroid Build Coastguard Worker return moduleSet 95*1fa6dee9SAndroid Build Coastguard Worker} 96*1fa6dee9SAndroid Build Coastguard Worker 97*1fa6dee9SAndroid Build Coastguard Worker// AllModules returns a ModuleSet that contains all modules in the Blueprint. 98*1fa6dee9SAndroid Build Coastguard Workerfunc (bp *Blueprint) AllModules() *ModuleSet { 99*1fa6dee9SAndroid Build Coastguard Worker moduleSet := &ModuleSet{ 100*1fa6dee9SAndroid Build Coastguard Worker bp: bp, 101*1fa6dee9SAndroid Build Coastguard Worker } 102*1fa6dee9SAndroid Build Coastguard Worker for _, def := range bp.bpFile.Defs { 103*1fa6dee9SAndroid Build Coastguard Worker module, ok := def.(*parser.Module) 104*1fa6dee9SAndroid Build Coastguard Worker if !ok { 105*1fa6dee9SAndroid Build Coastguard Worker continue 106*1fa6dee9SAndroid Build Coastguard Worker } 107*1fa6dee9SAndroid Build Coastguard Worker 108*1fa6dee9SAndroid Build Coastguard Worker moduleSet.modules = append(moduleSet.modules, module) 109*1fa6dee9SAndroid Build Coastguard Worker } 110*1fa6dee9SAndroid Build Coastguard Worker 111*1fa6dee9SAndroid Build Coastguard Worker return moduleSet 112*1fa6dee9SAndroid Build Coastguard Worker} 113*1fa6dee9SAndroid Build Coastguard Worker 114*1fa6dee9SAndroid Build Coastguard Worker// A ModuleSet represents a set of modules in a Blueprint, and can be used to make modifications 115*1fa6dee9SAndroid Build Coastguard Worker// the modules. 116*1fa6dee9SAndroid Build Coastguard Workertype ModuleSet struct { 117*1fa6dee9SAndroid Build Coastguard Worker bp *Blueprint 118*1fa6dee9SAndroid Build Coastguard Worker modules []*parser.Module 119*1fa6dee9SAndroid Build Coastguard Worker} 120*1fa6dee9SAndroid Build Coastguard Worker 121*1fa6dee9SAndroid Build Coastguard Worker// GetProperty returns a PropertySet that contains all properties with the given list of names 122*1fa6dee9SAndroid Build Coastguard Worker// in all modules in the ModuleSet. Requesting properties that do not exist is not an error. 123*1fa6dee9SAndroid Build Coastguard Worker// It returns an error for a malformed property name, or if the requested property is nested 124*1fa6dee9SAndroid Build Coastguard Worker// in a property that is not a map. 125*1fa6dee9SAndroid Build Coastguard Workerfunc (ms *ModuleSet) GetProperty(properties ...string) (*PropertySet, error) { 126*1fa6dee9SAndroid Build Coastguard Worker propertySet := &PropertySet{ 127*1fa6dee9SAndroid Build Coastguard Worker bp: ms.bp, 128*1fa6dee9SAndroid Build Coastguard Worker } 129*1fa6dee9SAndroid Build Coastguard Worker 130*1fa6dee9SAndroid Build Coastguard Worker targetProperties, err := parseQualifiedProperties(properties) 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 135*1fa6dee9SAndroid Build Coastguard Worker for _, targetProperty := range targetProperties { 136*1fa6dee9SAndroid Build Coastguard Worker for _, module := range ms.modules { 137*1fa6dee9SAndroid Build Coastguard Worker prop, _, err := getRecursiveProperty(module, targetProperty) 138*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 139*1fa6dee9SAndroid Build Coastguard Worker return nil, err 140*1fa6dee9SAndroid Build Coastguard Worker } else if prop == nil { 141*1fa6dee9SAndroid Build Coastguard Worker continue 142*1fa6dee9SAndroid Build Coastguard Worker } 143*1fa6dee9SAndroid Build Coastguard Worker propertySet.properties = append(propertySet.properties, &property{ 144*1fa6dee9SAndroid Build Coastguard Worker property: prop, 145*1fa6dee9SAndroid Build Coastguard Worker module: module, 146*1fa6dee9SAndroid Build Coastguard Worker name: targetProperty, 147*1fa6dee9SAndroid Build Coastguard Worker }) 148*1fa6dee9SAndroid Build Coastguard Worker } 149*1fa6dee9SAndroid Build Coastguard Worker } 150*1fa6dee9SAndroid Build Coastguard Worker 151*1fa6dee9SAndroid Build Coastguard Worker return propertySet, nil 152*1fa6dee9SAndroid Build Coastguard Worker} 153*1fa6dee9SAndroid Build Coastguard Worker 154*1fa6dee9SAndroid Build Coastguard Worker// GetOrCreateProperty returns a PropertySet that contains all properties with the given list of names 155*1fa6dee9SAndroid Build Coastguard Worker// in all modules in the ModuleSet, creating empty placeholder properties if they don't exist. 156*1fa6dee9SAndroid Build Coastguard Worker// It returns an error for a malformed property name, or if the requested property is nested 157*1fa6dee9SAndroid Build Coastguard Worker// in a property that is not a map. 158*1fa6dee9SAndroid Build Coastguard Workerfunc (ms *ModuleSet) GetOrCreateProperty(typ Type, properties ...string) (*PropertySet, error) { 159*1fa6dee9SAndroid Build Coastguard Worker propertySet := &PropertySet{ 160*1fa6dee9SAndroid Build Coastguard Worker bp: ms.bp, 161*1fa6dee9SAndroid Build Coastguard Worker } 162*1fa6dee9SAndroid Build Coastguard Worker 163*1fa6dee9SAndroid Build Coastguard Worker targetProperties, err := parseQualifiedProperties(properties) 164*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 165*1fa6dee9SAndroid Build Coastguard Worker return nil, err 166*1fa6dee9SAndroid Build Coastguard Worker } 167*1fa6dee9SAndroid Build Coastguard Worker 168*1fa6dee9SAndroid Build Coastguard Worker for _, targetProperty := range targetProperties { 169*1fa6dee9SAndroid Build Coastguard Worker for _, module := range ms.modules { 170*1fa6dee9SAndroid Build Coastguard Worker prop, _, err := getRecursiveProperty(module, targetProperty) 171*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 172*1fa6dee9SAndroid Build Coastguard Worker return nil, err 173*1fa6dee9SAndroid Build Coastguard Worker } else if prop == nil { 174*1fa6dee9SAndroid Build Coastguard Worker prop, err = createRecursiveProperty(module, targetProperty, parser.ZeroExpression(parser.Type(typ))) 175*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 176*1fa6dee9SAndroid Build Coastguard Worker return nil, err 177*1fa6dee9SAndroid Build Coastguard Worker } 178*1fa6dee9SAndroid Build Coastguard Worker ms.bp.modified = true 179*1fa6dee9SAndroid Build Coastguard Worker } else { 180*1fa6dee9SAndroid Build Coastguard Worker if prop.Value.Type() != parser.Type(typ) { 181*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("unexpected type found in property %q, wanted %s, found %s", 182*1fa6dee9SAndroid Build Coastguard Worker targetProperty.String(), typ, prop.Value.Type()) 183*1fa6dee9SAndroid Build Coastguard Worker } 184*1fa6dee9SAndroid Build Coastguard Worker } 185*1fa6dee9SAndroid Build Coastguard Worker propertySet.properties = append(propertySet.properties, &property{ 186*1fa6dee9SAndroid Build Coastguard Worker property: prop, 187*1fa6dee9SAndroid Build Coastguard Worker module: module, 188*1fa6dee9SAndroid Build Coastguard Worker name: targetProperty, 189*1fa6dee9SAndroid Build Coastguard Worker }) 190*1fa6dee9SAndroid Build Coastguard Worker } 191*1fa6dee9SAndroid Build Coastguard Worker } 192*1fa6dee9SAndroid Build Coastguard Worker 193*1fa6dee9SAndroid Build Coastguard Worker return propertySet, nil 194*1fa6dee9SAndroid Build Coastguard Worker} 195*1fa6dee9SAndroid Build Coastguard Worker 196*1fa6dee9SAndroid Build Coastguard Worker// RemoveProperty removes the given list of properties from all modules in the ModuleSet. 197*1fa6dee9SAndroid Build Coastguard Worker// It returns an error for a malformed property name, or if the requested property is nested 198*1fa6dee9SAndroid Build Coastguard Worker// in a property that is not a map. Removing a property that does not exist is not an error. 199*1fa6dee9SAndroid Build Coastguard Workerfunc (ms *ModuleSet) RemoveProperty(properties ...string) error { 200*1fa6dee9SAndroid Build Coastguard Worker targetProperties, err := parseQualifiedProperties(properties) 201*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 202*1fa6dee9SAndroid Build Coastguard Worker return err 203*1fa6dee9SAndroid Build Coastguard Worker } 204*1fa6dee9SAndroid Build Coastguard Worker 205*1fa6dee9SAndroid Build Coastguard Worker for _, targetProperty := range targetProperties { 206*1fa6dee9SAndroid Build Coastguard Worker for _, module := range ms.modules { 207*1fa6dee9SAndroid Build Coastguard Worker prop, parent, err := getRecursiveProperty(module, targetProperty) 208*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 209*1fa6dee9SAndroid Build Coastguard Worker return err 210*1fa6dee9SAndroid Build Coastguard Worker } else if prop != nil { 211*1fa6dee9SAndroid Build Coastguard Worker parent.RemoveProperty(prop.Name) 212*1fa6dee9SAndroid Build Coastguard Worker ms.bp.modified = true 213*1fa6dee9SAndroid Build Coastguard Worker } 214*1fa6dee9SAndroid Build Coastguard Worker } 215*1fa6dee9SAndroid Build Coastguard Worker } 216*1fa6dee9SAndroid Build Coastguard Worker return nil 217*1fa6dee9SAndroid Build Coastguard Worker} 218*1fa6dee9SAndroid Build Coastguard Worker 219*1fa6dee9SAndroid Build Coastguard Worker// MoveProperty moves the given list of properties to a new parent property. 220*1fa6dee9SAndroid Build Coastguard Worker// It returns an error for a malformed property name, or if the requested property is nested 221*1fa6dee9SAndroid Build Coastguard Worker// in a property that is not a map. Moving a property that does not exist is not an error. 222*1fa6dee9SAndroid Build Coastguard Workerfunc (ms *ModuleSet) MoveProperty(newParent string, properties ...string) error { 223*1fa6dee9SAndroid Build Coastguard Worker targetProperties, err := parseQualifiedProperties(properties) 224*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 225*1fa6dee9SAndroid Build Coastguard Worker return err 226*1fa6dee9SAndroid Build Coastguard Worker } 227*1fa6dee9SAndroid Build Coastguard Worker 228*1fa6dee9SAndroid Build Coastguard Worker for _, targetProperty := range targetProperties { 229*1fa6dee9SAndroid Build Coastguard Worker for _, module := range ms.modules { 230*1fa6dee9SAndroid Build Coastguard Worker prop, parent, err := getRecursiveProperty(module, targetProperty) 231*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 232*1fa6dee9SAndroid Build Coastguard Worker return err 233*1fa6dee9SAndroid Build Coastguard Worker } else if prop != nil { 234*1fa6dee9SAndroid Build Coastguard Worker parent.MovePropertyContents(prop.Name, newParent) 235*1fa6dee9SAndroid Build Coastguard Worker ms.bp.modified = true 236*1fa6dee9SAndroid Build Coastguard Worker } 237*1fa6dee9SAndroid Build Coastguard Worker } 238*1fa6dee9SAndroid Build Coastguard Worker } 239*1fa6dee9SAndroid Build Coastguard Worker return nil 240*1fa6dee9SAndroid Build Coastguard Worker} 241*1fa6dee9SAndroid Build Coastguard Worker 242*1fa6dee9SAndroid Build Coastguard Worker// PropertySet represents a set of properties in a set of modules. 243*1fa6dee9SAndroid Build Coastguard Workertype PropertySet struct { 244*1fa6dee9SAndroid Build Coastguard Worker bp *Blueprint 245*1fa6dee9SAndroid Build Coastguard Worker properties []*property 246*1fa6dee9SAndroid Build Coastguard Worker sortLists bool 247*1fa6dee9SAndroid Build Coastguard Worker} 248*1fa6dee9SAndroid Build Coastguard Worker 249*1fa6dee9SAndroid Build Coastguard Workertype property struct { 250*1fa6dee9SAndroid Build Coastguard Worker property *parser.Property 251*1fa6dee9SAndroid Build Coastguard Worker module *parser.Module 252*1fa6dee9SAndroid Build Coastguard Worker name *qualifiedProperty 253*1fa6dee9SAndroid Build Coastguard Worker} 254*1fa6dee9SAndroid Build Coastguard Worker 255*1fa6dee9SAndroid Build Coastguard Worker// SortListsWhenModifying causes any future modifications to lists in the PropertySet to sort 256*1fa6dee9SAndroid Build Coastguard Worker// the lists. Otherwise, lists are only sorted if they appear to be sorted before modification. 257*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) SortListsWhenModifying() { 258*1fa6dee9SAndroid Build Coastguard Worker ps.sortLists = true 259*1fa6dee9SAndroid Build Coastguard Worker} 260*1fa6dee9SAndroid Build Coastguard Worker 261*1fa6dee9SAndroid Build Coastguard Worker// SetString sets all properties in the PropertySet to the given string. It returns an error 262*1fa6dee9SAndroid Build Coastguard Worker// if any of the properties are not strings. 263*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) SetString(s string) error { 264*1fa6dee9SAndroid Build Coastguard Worker var errs []error 265*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 266*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 267*1fa6dee9SAndroid Build Coastguard Worker str, ok := value.(*parser.String) 268*1fa6dee9SAndroid Build Coastguard Worker if !ok { 269*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be string, found %s", 270*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type().String())) 271*1fa6dee9SAndroid Build Coastguard Worker continue 272*1fa6dee9SAndroid Build Coastguard Worker } 273*1fa6dee9SAndroid Build Coastguard Worker if str.Value != s { 274*1fa6dee9SAndroid Build Coastguard Worker str.Value = s 275*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 276*1fa6dee9SAndroid Build Coastguard Worker } 277*1fa6dee9SAndroid Build Coastguard Worker } 278*1fa6dee9SAndroid Build Coastguard Worker 279*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 280*1fa6dee9SAndroid Build Coastguard Worker} 281*1fa6dee9SAndroid Build Coastguard Worker 282*1fa6dee9SAndroid Build Coastguard Worker// SetBool sets all properties in the PropertySet to the given boolean. It returns an error 283*1fa6dee9SAndroid Build Coastguard Worker// if any of the properties are not booleans. 284*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) SetBool(b bool) error { 285*1fa6dee9SAndroid Build Coastguard Worker var errs []error 286*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 287*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 288*1fa6dee9SAndroid Build Coastguard Worker res, ok := value.(*parser.Bool) 289*1fa6dee9SAndroid Build Coastguard Worker if !ok { 290*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be bool, found %s", 291*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type().String())) 292*1fa6dee9SAndroid Build Coastguard Worker continue 293*1fa6dee9SAndroid Build Coastguard Worker } 294*1fa6dee9SAndroid Build Coastguard Worker if res.Value != b { 295*1fa6dee9SAndroid Build Coastguard Worker res.Value = b 296*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 297*1fa6dee9SAndroid Build Coastguard Worker } 298*1fa6dee9SAndroid Build Coastguard Worker } 299*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 300*1fa6dee9SAndroid Build Coastguard Worker} 301*1fa6dee9SAndroid Build Coastguard Worker 302*1fa6dee9SAndroid Build Coastguard Worker// AddStringToList adds the given strings to all properties in the PropertySet. It returns an error 303*1fa6dee9SAndroid Build Coastguard Worker// if any of the properties are not lists of strings. 304*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) AddStringToList(strs ...string) error { 305*1fa6dee9SAndroid Build Coastguard Worker var errs []error 306*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 307*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 308*1fa6dee9SAndroid Build Coastguard Worker list, ok := value.(*parser.List) 309*1fa6dee9SAndroid Build Coastguard Worker if !ok { 310*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be list, found %s", 311*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type())) 312*1fa6dee9SAndroid Build Coastguard Worker continue 313*1fa6dee9SAndroid Build Coastguard Worker } 314*1fa6dee9SAndroid Build Coastguard Worker wasSorted := parser.ListIsSorted(list) 315*1fa6dee9SAndroid Build Coastguard Worker modified := false 316*1fa6dee9SAndroid Build Coastguard Worker for _, s := range strs { 317*1fa6dee9SAndroid Build Coastguard Worker m := parser.AddStringToList(list, s) 318*1fa6dee9SAndroid Build Coastguard Worker modified = modified || m 319*1fa6dee9SAndroid Build Coastguard Worker } 320*1fa6dee9SAndroid Build Coastguard Worker if modified { 321*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 322*1fa6dee9SAndroid Build Coastguard Worker if wasSorted || ps.sortLists { 323*1fa6dee9SAndroid Build Coastguard Worker parser.SortList(ps.bp.bpFile, list) 324*1fa6dee9SAndroid Build Coastguard Worker } 325*1fa6dee9SAndroid Build Coastguard Worker } 326*1fa6dee9SAndroid Build Coastguard Worker } 327*1fa6dee9SAndroid Build Coastguard Worker 328*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 329*1fa6dee9SAndroid Build Coastguard Worker} 330*1fa6dee9SAndroid Build Coastguard Worker 331*1fa6dee9SAndroid Build Coastguard Worker// RemoveStringFromList removes the given strings to all properties in the PropertySet if they are present. 332*1fa6dee9SAndroid Build Coastguard Worker// It returns an error if any of the properties are not lists of strings. 333*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) RemoveStringFromList(strs ...string) error { 334*1fa6dee9SAndroid Build Coastguard Worker var errs []error 335*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 336*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 337*1fa6dee9SAndroid Build Coastguard Worker list, ok := value.(*parser.List) 338*1fa6dee9SAndroid Build Coastguard Worker if !ok { 339*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be list, found %s", 340*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type())) 341*1fa6dee9SAndroid Build Coastguard Worker continue 342*1fa6dee9SAndroid Build Coastguard Worker } 343*1fa6dee9SAndroid Build Coastguard Worker wasSorted := parser.ListIsSorted(list) 344*1fa6dee9SAndroid Build Coastguard Worker modified := false 345*1fa6dee9SAndroid Build Coastguard Worker for _, s := range strs { 346*1fa6dee9SAndroid Build Coastguard Worker m := parser.RemoveStringFromList(list, s) 347*1fa6dee9SAndroid Build Coastguard Worker modified = modified || m 348*1fa6dee9SAndroid Build Coastguard Worker } 349*1fa6dee9SAndroid Build Coastguard Worker if modified { 350*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 351*1fa6dee9SAndroid Build Coastguard Worker if wasSorted || ps.sortLists { 352*1fa6dee9SAndroid Build Coastguard Worker parser.SortList(ps.bp.bpFile, list) 353*1fa6dee9SAndroid Build Coastguard Worker } 354*1fa6dee9SAndroid Build Coastguard Worker } 355*1fa6dee9SAndroid Build Coastguard Worker } 356*1fa6dee9SAndroid Build Coastguard Worker 357*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 358*1fa6dee9SAndroid Build Coastguard Worker} 359*1fa6dee9SAndroid Build Coastguard Worker 360*1fa6dee9SAndroid Build Coastguard Worker// AddLiteral adds the given literal blueprint snippet to all properties in the PropertySet if they are present. 361*1fa6dee9SAndroid Build Coastguard Worker// It returns an error if any of the properties are not lists. 362*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) AddLiteral(s string) error { 363*1fa6dee9SAndroid Build Coastguard Worker var errs []error 364*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 365*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 366*1fa6dee9SAndroid Build Coastguard Worker if ps.sortLists { 367*1fa6dee9SAndroid Build Coastguard Worker return fmt.Errorf("sorting not supported when adding a literal") 368*1fa6dee9SAndroid Build Coastguard Worker } 369*1fa6dee9SAndroid Build Coastguard Worker list, ok := value.(*parser.List) 370*1fa6dee9SAndroid Build Coastguard Worker if !ok { 371*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be list, found %s", 372*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type().String())) 373*1fa6dee9SAndroid Build Coastguard Worker continue 374*1fa6dee9SAndroid Build Coastguard Worker } 375*1fa6dee9SAndroid Build Coastguard Worker value, parseErrs := parser.ParseExpression(strings.NewReader(s)) 376*1fa6dee9SAndroid Build Coastguard Worker if len(parseErrs) > 0 { 377*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, parseErrs...) 378*1fa6dee9SAndroid Build Coastguard Worker continue 379*1fa6dee9SAndroid Build Coastguard Worker } 380*1fa6dee9SAndroid Build Coastguard Worker list.Values = append(list.Values, value) 381*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 382*1fa6dee9SAndroid Build Coastguard Worker } 383*1fa6dee9SAndroid Build Coastguard Worker 384*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 385*1fa6dee9SAndroid Build Coastguard Worker} 386*1fa6dee9SAndroid Build Coastguard Worker 387*1fa6dee9SAndroid Build Coastguard Worker// ReplaceStrings applies replacements to all properties in the PropertySet. It replaces all instances 388*1fa6dee9SAndroid Build Coastguard Worker// of the strings in the keys of the given map with their corresponding values. It returns an error 389*1fa6dee9SAndroid Build Coastguard Worker// if any of the properties are not lists of strings. 390*1fa6dee9SAndroid Build Coastguard Workerfunc (ps *PropertySet) ReplaceStrings(replacements map[string]string) error { 391*1fa6dee9SAndroid Build Coastguard Worker var errs []error 392*1fa6dee9SAndroid Build Coastguard Worker for _, prop := range ps.properties { 393*1fa6dee9SAndroid Build Coastguard Worker value := prop.property.Value 394*1fa6dee9SAndroid Build Coastguard Worker if list, ok := value.(*parser.List); ok { 395*1fa6dee9SAndroid Build Coastguard Worker modified := parser.ReplaceStringsInList(list, replacements) 396*1fa6dee9SAndroid Build Coastguard Worker if modified { 397*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 398*1fa6dee9SAndroid Build Coastguard Worker } 399*1fa6dee9SAndroid Build Coastguard Worker } else if str, ok := value.(*parser.String); ok { 400*1fa6dee9SAndroid Build Coastguard Worker oldVal := str.Value 401*1fa6dee9SAndroid Build Coastguard Worker replacementValue := replacements[oldVal] 402*1fa6dee9SAndroid Build Coastguard Worker if replacementValue != "" { 403*1fa6dee9SAndroid Build Coastguard Worker str.Value = replacementValue 404*1fa6dee9SAndroid Build Coastguard Worker ps.bp.modified = true 405*1fa6dee9SAndroid Build Coastguard Worker } 406*1fa6dee9SAndroid Build Coastguard Worker } else { 407*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, fmt.Errorf("expected property %s in module %s to be a list or string, found %s", 408*1fa6dee9SAndroid Build Coastguard Worker prop.name, prop.module.Name(), value.Type().String())) 409*1fa6dee9SAndroid Build Coastguard Worker } 410*1fa6dee9SAndroid Build Coastguard Worker } 411*1fa6dee9SAndroid Build Coastguard Worker return errors.Join(errs...) 412*1fa6dee9SAndroid Build Coastguard Worker} 413*1fa6dee9SAndroid Build Coastguard Worker 414*1fa6dee9SAndroid Build Coastguard Workerfunc getRecursiveProperty(module *parser.Module, property *qualifiedProperty) (prop *parser.Property, 415*1fa6dee9SAndroid Build Coastguard Worker parent *parser.Map, err error) { 416*1fa6dee9SAndroid Build Coastguard Worker 417*1fa6dee9SAndroid Build Coastguard Worker parent, err = traverseToQualifiedPropertyParent(module, property, false) 418*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 419*1fa6dee9SAndroid Build Coastguard Worker return nil, nil, err 420*1fa6dee9SAndroid Build Coastguard Worker } 421*1fa6dee9SAndroid Build Coastguard Worker if parent == nil { 422*1fa6dee9SAndroid Build Coastguard Worker return nil, nil, nil 423*1fa6dee9SAndroid Build Coastguard Worker } 424*1fa6dee9SAndroid Build Coastguard Worker if prop, found := parent.GetProperty(property.name()); found { 425*1fa6dee9SAndroid Build Coastguard Worker return prop, parent, nil 426*1fa6dee9SAndroid Build Coastguard Worker } 427*1fa6dee9SAndroid Build Coastguard Worker 428*1fa6dee9SAndroid Build Coastguard Worker return nil, nil, nil 429*1fa6dee9SAndroid Build Coastguard Worker} 430*1fa6dee9SAndroid Build Coastguard Worker 431*1fa6dee9SAndroid Build Coastguard Workerfunc createRecursiveProperty(module *parser.Module, property *qualifiedProperty, 432*1fa6dee9SAndroid Build Coastguard Worker value parser.Expression) (prop *parser.Property, err error) { 433*1fa6dee9SAndroid Build Coastguard Worker parent, err := traverseToQualifiedPropertyParent(module, property, true) 434*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 435*1fa6dee9SAndroid Build Coastguard Worker return nil, err 436*1fa6dee9SAndroid Build Coastguard Worker } 437*1fa6dee9SAndroid Build Coastguard Worker if _, found := parent.GetProperty(property.name()); found { 438*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("property %q already exists", property.String()) 439*1fa6dee9SAndroid Build Coastguard Worker } 440*1fa6dee9SAndroid Build Coastguard Worker 441*1fa6dee9SAndroid Build Coastguard Worker prop = &parser.Property{Name: property.name(), Value: value} 442*1fa6dee9SAndroid Build Coastguard Worker parent.Properties = append(parent.Properties, prop) 443*1fa6dee9SAndroid Build Coastguard Worker return prop, nil 444*1fa6dee9SAndroid Build Coastguard Worker} 445*1fa6dee9SAndroid Build Coastguard Worker 446*1fa6dee9SAndroid Build Coastguard Workerfunc traverseToQualifiedPropertyParent(module *parser.Module, property *qualifiedProperty, 447*1fa6dee9SAndroid Build Coastguard Worker create bool) (parent *parser.Map, err error) { 448*1fa6dee9SAndroid Build Coastguard Worker m := &module.Map 449*1fa6dee9SAndroid Build Coastguard Worker for i, prefix := range property.prefixes() { 450*1fa6dee9SAndroid Build Coastguard Worker if prop, found := m.GetProperty(prefix); found { 451*1fa6dee9SAndroid Build Coastguard Worker if mm, ok := prop.Value.(*parser.Map); ok { 452*1fa6dee9SAndroid Build Coastguard Worker m = mm 453*1fa6dee9SAndroid Build Coastguard Worker } else { 454*1fa6dee9SAndroid Build Coastguard Worker // We've found a property in the AST and such property is not of type *parser.Map 455*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("Expected property %q to be a map, found %s", 456*1fa6dee9SAndroid Build Coastguard Worker strings.Join(property.prefixes()[:i+1], "."), prop.Value.Type()) 457*1fa6dee9SAndroid Build Coastguard Worker } 458*1fa6dee9SAndroid Build Coastguard Worker } else if create { 459*1fa6dee9SAndroid Build Coastguard Worker mm := &parser.Map{} 460*1fa6dee9SAndroid Build Coastguard Worker m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm}) 461*1fa6dee9SAndroid Build Coastguard Worker m = mm 462*1fa6dee9SAndroid Build Coastguard Worker } else { 463*1fa6dee9SAndroid Build Coastguard Worker return nil, nil 464*1fa6dee9SAndroid Build Coastguard Worker } 465*1fa6dee9SAndroid Build Coastguard Worker } 466*1fa6dee9SAndroid Build Coastguard Worker return m, nil 467*1fa6dee9SAndroid Build Coastguard Worker} 468*1fa6dee9SAndroid Build Coastguard Worker 469*1fa6dee9SAndroid Build Coastguard Workertype qualifiedProperty struct { 470*1fa6dee9SAndroid Build Coastguard Worker parts []string 471*1fa6dee9SAndroid Build Coastguard Worker} 472*1fa6dee9SAndroid Build Coastguard Worker 473*1fa6dee9SAndroid Build Coastguard Workerfunc (p *qualifiedProperty) name() string { 474*1fa6dee9SAndroid Build Coastguard Worker return p.parts[len(p.parts)-1] 475*1fa6dee9SAndroid Build Coastguard Worker} 476*1fa6dee9SAndroid Build Coastguard Workerfunc (p *qualifiedProperty) prefixes() []string { 477*1fa6dee9SAndroid Build Coastguard Worker return p.parts[:len(p.parts)-1] 478*1fa6dee9SAndroid Build Coastguard Worker} 479*1fa6dee9SAndroid Build Coastguard Workerfunc (p *qualifiedProperty) String() string { 480*1fa6dee9SAndroid Build Coastguard Worker return strings.Join(p.parts, ".") 481*1fa6dee9SAndroid Build Coastguard Worker} 482*1fa6dee9SAndroid Build Coastguard Worker 483*1fa6dee9SAndroid Build Coastguard Workerfunc parseQualifiedProperty(s string) (*qualifiedProperty, error) { 484*1fa6dee9SAndroid Build Coastguard Worker parts := strings.Split(s, ".") 485*1fa6dee9SAndroid Build Coastguard Worker if len(parts) == 0 { 486*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("%q is not a valid property name", s) 487*1fa6dee9SAndroid Build Coastguard Worker } 488*1fa6dee9SAndroid Build Coastguard Worker for _, part := range parts { 489*1fa6dee9SAndroid Build Coastguard Worker if part == "" { 490*1fa6dee9SAndroid Build Coastguard Worker return nil, fmt.Errorf("%q is not a valid property name", s) 491*1fa6dee9SAndroid Build Coastguard Worker } 492*1fa6dee9SAndroid Build Coastguard Worker } 493*1fa6dee9SAndroid Build Coastguard Worker prop := qualifiedProperty{parts} 494*1fa6dee9SAndroid Build Coastguard Worker return &prop, nil 495*1fa6dee9SAndroid Build Coastguard Worker 496*1fa6dee9SAndroid Build Coastguard Worker} 497*1fa6dee9SAndroid Build Coastguard Worker 498*1fa6dee9SAndroid Build Coastguard Workerfunc parseQualifiedProperties(properties []string) ([]*qualifiedProperty, error) { 499*1fa6dee9SAndroid Build Coastguard Worker var qualifiedProperties []*qualifiedProperty 500*1fa6dee9SAndroid Build Coastguard Worker var errs []error 501*1fa6dee9SAndroid Build Coastguard Worker for _, property := range properties { 502*1fa6dee9SAndroid Build Coastguard Worker qualifiedProperty, err := parseQualifiedProperty(property) 503*1fa6dee9SAndroid Build Coastguard Worker if err != nil { 504*1fa6dee9SAndroid Build Coastguard Worker errs = append(errs, err) 505*1fa6dee9SAndroid Build Coastguard Worker } 506*1fa6dee9SAndroid Build Coastguard Worker qualifiedProperties = append(qualifiedProperties, qualifiedProperty) 507*1fa6dee9SAndroid Build Coastguard Worker } 508*1fa6dee9SAndroid Build Coastguard Worker if len(errs) > 0 { 509*1fa6dee9SAndroid Build Coastguard Worker return nil, errors.Join(errs...) 510*1fa6dee9SAndroid Build Coastguard Worker } 511*1fa6dee9SAndroid Build Coastguard Worker return qualifiedProperties, nil 512*1fa6dee9SAndroid Build Coastguard Worker} 513*1fa6dee9SAndroid Build Coastguard Worker 514*1fa6dee9SAndroid Build Coastguard Workertype Type parser.Type 515*1fa6dee9SAndroid Build Coastguard Worker 516*1fa6dee9SAndroid Build Coastguard Workervar ( 517*1fa6dee9SAndroid Build Coastguard Worker List = Type(parser.ListType) 518*1fa6dee9SAndroid Build Coastguard Worker String = Type(parser.StringType) 519*1fa6dee9SAndroid Build Coastguard Worker Bool = Type(parser.BoolType) 520*1fa6dee9SAndroid Build Coastguard Worker Int64 = Type(parser.Int64Type) 521*1fa6dee9SAndroid Build Coastguard Worker Map = Type(parser.MapType) 522*1fa6dee9SAndroid Build Coastguard Worker) 523*1fa6dee9SAndroid Build Coastguard Worker 524*1fa6dee9SAndroid Build Coastguard Workerfunc (t Type) String() string { 525*1fa6dee9SAndroid Build Coastguard Worker return parser.Type(t).String() 526*1fa6dee9SAndroid Build Coastguard Worker} 527