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