xref: /aosp_15_r20/build/blueprint/bpmodify/bpmodify.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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