xref: /aosp_15_r20/build/blueprint/bpmodify/cmd/main.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1// Mostly copied from Go's src/cmd/gofmt:
2// Copyright 2009 The Go Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package main
7
8import (
9	"flag"
10	"fmt"
11	"io"
12	"io/ioutil"
13	"os"
14	"os/exec"
15	"path/filepath"
16	"strings"
17	"syscall"
18	"unicode"
19
20	"github.com/google/blueprint/bpmodify"
21)
22
23var (
24	// main operation modes
25	list               = flag.Bool("l", false, "list files that would be modified by bpmodify")
26	write              = flag.Bool("w", false, "write result to (source) file instead of stdout")
27	doDiff             = flag.Bool("d", false, "display diffs instead of rewriting files")
28	sortLists          = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
29	targetedModules    = new(identSet)
30	targetedProperties = new(identSet)
31	addIdents          = new(identSet)
32	removeIdents       = new(identSet)
33	removeProperty     = flag.Bool("remove-property", false, "remove the property")
34	moveProperty       = flag.Bool("move-property", false, "moves contents of property into newLocation")
35	newLocation        string
36	setString          *string
37	addLiteral         *string
38	setBool            *string
39	replaceProperty    = new(replacements)
40)
41
42func init() {
43	flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
44	flag.Var(targetedProperties, "parameter", "alias to -property=`name1[,name2[,... […]")
45	flag.StringVar(&newLocation, "new-location", "", " use with moveProperty to move contents of -property into a property with name -new-location ")
46	flag.Var(targetedProperties, "property", "comma-separated list of fully qualified `name`s of properties to modify (default \"deps\")")
47	flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
48	flag.Var(stringPtrFlag{&addLiteral}, "add-literal", "a literal to add to a list")
49	flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
50	flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
51	flag.Var(replaceProperty, "replace-property", "property names to be replaced, in the form of oldName1=newName1,oldName2=newName2")
52	flag.Var(stringPtrFlag{&setBool}, "set-bool", "a boolean value to set a property with (not a list)")
53	flag.Usage = usage
54}
55
56var (
57	exitCode = 0
58)
59
60func report(err error) {
61	fmt.Fprintln(os.Stderr, err)
62	exitCode = 2
63}
64
65func usage() {
66	fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [path ...]\n", os.Args[0])
67	flag.PrintDefaults()
68}
69
70func processBp(bp *bpmodify.Blueprint) error {
71	var modules *bpmodify.ModuleSet
72	if targetedModules.all {
73		modules = bp.AllModules()
74	} else {
75		modules = bp.ModulesByName(targetedModules.idents...)
76	}
77
78	if *removeProperty {
79		// remove-property is used solely, so return here.
80		return modules.RemoveProperty(targetedProperties.idents...)
81	} else if *moveProperty {
82		return modules.MoveProperty(newLocation, targetedProperties.idents...)
83	} else if len(addIdents.idents) > 0 {
84		props, err := modules.GetOrCreateProperty(bpmodify.List, targetedProperties.idents...)
85		if err != nil {
86			return err
87		}
88		return props.AddStringToList(addIdents.idents...)
89	} else if addLiteral != nil {
90		props, err := modules.GetOrCreateProperty(bpmodify.List, targetedProperties.idents...)
91		if err != nil {
92			return err
93		}
94		return props.AddLiteral(*addLiteral)
95	} else if setString != nil {
96		props, err := modules.GetOrCreateProperty(bpmodify.String, targetedProperties.idents...)
97		if err != nil {
98			return err
99		}
100		return props.SetString(*setString)
101	} else if setBool != nil {
102		props, err := modules.GetOrCreateProperty(bpmodify.Bool, targetedProperties.idents...)
103		if err != nil {
104			return err
105		}
106		var value bool
107		if *setBool == "true" {
108			value = true
109		} else if *setBool == "false" {
110			value = false
111		} else {
112			return fmt.Errorf("expected parameter to be true or false, found %s", *setBool)
113		}
114		return props.SetBool(value)
115	} else {
116		props, err := modules.GetProperty(targetedProperties.idents...)
117		if err != nil {
118			return err
119		}
120		if len(removeIdents.idents) > 0 {
121			return props.RemoveStringFromList(removeIdents.idents...)
122		} else if replaceProperty.size() != 0 {
123			return props.ReplaceStrings(replaceProperty.oldNameToNewName)
124		}
125	}
126
127	return nil
128}
129
130// If in == nil, the source is the contents of the file with the given filename.
131func processFile(filename string, in io.Reader, out io.Writer) error {
132	if in == nil {
133		f, err := os.Open(filename)
134		if err != nil {
135			return err
136		}
137		defer f.Close()
138		if *write {
139			syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
140		}
141		in = f
142	}
143
144	src, err := io.ReadAll(in)
145	if err != nil {
146		return err
147	}
148
149	bp, err := bpmodify.NewBlueprint(filename, src)
150	if err != nil {
151		return err
152	}
153
154	err = processBp(bp)
155	if err != nil {
156		return err
157	}
158
159	res, err := bp.Bytes()
160	if err != nil {
161		return err
162	}
163	if *list {
164		fmt.Fprintln(out, filename)
165	}
166	if *write {
167		err = os.WriteFile(filename, res, 0644)
168		if err != nil {
169			return err
170		}
171	}
172	if *doDiff {
173		data, err := diff(src, res)
174		if err != nil {
175			return fmt.Errorf("computing diff: %s", err)
176		}
177		fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
178		out.Write(data)
179	}
180	if !*list && !*write && !*doDiff {
181		_, err = out.Write(res)
182	}
183
184	return err
185}
186
187func visitFile(path string, f os.FileInfo, err error) error {
188	//TODO(dacek): figure out a better way to target intended .bp files without parsing errors
189	if err == nil && (f.Name() == "Blueprints" || strings.HasSuffix(f.Name(), ".bp")) {
190		err = processFile(path, nil, os.Stdout)
191	}
192	if err != nil {
193		report(err)
194	}
195	return nil
196}
197
198func walkDir(path string) {
199	filepath.Walk(path, visitFile)
200}
201
202func main() {
203	defer func() {
204		if err := recover(); err != nil {
205			report(fmt.Errorf("error: %s", err))
206		}
207		os.Exit(exitCode)
208	}()
209	flag.Parse()
210
211	if len(targetedProperties.idents) == 0 && *moveProperty {
212		report(fmt.Errorf("-move-property must specify property"))
213		return
214	}
215
216	if len(targetedProperties.idents) == 0 {
217		targetedProperties.Set("deps")
218	}
219	if flag.NArg() == 0 {
220		if *write {
221			report(fmt.Errorf("error: cannot use -w with standard input"))
222			return
223		}
224		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
225			report(err)
226		}
227		return
228	}
229	if len(targetedModules.idents) == 0 {
230		report(fmt.Errorf("-m parameter is required"))
231		return
232	}
233
234	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty && !*moveProperty && (*replaceProperty).size() == 0 && setBool == nil {
235		report(fmt.Errorf("-a, -add-literal, -r, -remove-property, -move-property, replace-property or -str parameter is required"))
236		return
237	}
238	if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
239		report(fmt.Errorf("-remove-property cannot be used with other parameter(s)"))
240		return
241	}
242	if *moveProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
243		report(fmt.Errorf("-move-property cannot be used with other parameter(s)"))
244		return
245	}
246	if *moveProperty && newLocation == "" {
247		report(fmt.Errorf("-move-property must specify -new-location"))
248		return
249	}
250	for i := 0; i < flag.NArg(); i++ {
251		path := flag.Arg(i)
252		switch dir, err := os.Stat(path); {
253		case err != nil:
254			report(err)
255		case dir.IsDir():
256			walkDir(path)
257		default:
258			if err := processFile(path, nil, os.Stdout); err != nil {
259				report(err)
260			}
261		}
262	}
263}
264
265func diff(b1, b2 []byte) (data []byte, err error) {
266	f1, err := ioutil.TempFile("", "bpfmt")
267	if err != nil {
268		return
269	}
270	defer os.Remove(f1.Name())
271	defer f1.Close()
272	f2, err := ioutil.TempFile("", "bpfmt")
273	if err != nil {
274		return
275	}
276	defer os.Remove(f2.Name())
277	defer f2.Close()
278	f1.Write(b1)
279	f2.Write(b2)
280	data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
281	if len(data) > 0 {
282		// diff exits with a non-zero status when the files don't match.
283		// Ignore that failure as long as we get output.
284		err = nil
285	}
286	return
287}
288
289type stringPtrFlag struct {
290	s **string
291}
292
293func (f stringPtrFlag) Set(s string) error {
294	*f.s = &s
295	return nil
296}
297func (f stringPtrFlag) String() string {
298	if f.s == nil || *f.s == nil {
299		return ""
300	}
301	return **f.s
302}
303
304type replacements struct {
305	oldNameToNewName map[string]string
306}
307
308func (m *replacements) String() string {
309	ret := ""
310	sep := ""
311	for k, v := range m.oldNameToNewName {
312		ret += sep
313		ret += k
314		ret += ":"
315		ret += v
316		sep = ","
317	}
318	return ret
319}
320
321func (m *replacements) Set(s string) error {
322	usedNames := make(map[string]struct{})
323
324	pairs := strings.Split(s, ",")
325	length := len(pairs)
326	m.oldNameToNewName = make(map[string]string)
327	for i := 0; i < length; i++ {
328
329		pair := strings.SplitN(pairs[i], "=", 2)
330		if len(pair) != 2 {
331			return fmt.Errorf("Invalid replacement pair %s", pairs[i])
332		}
333		oldName := pair[0]
334		newName := pair[1]
335		if _, seen := usedNames[oldName]; seen {
336			return fmt.Errorf("Duplicated replacement name %s", oldName)
337		}
338		if _, seen := usedNames[newName]; seen {
339			return fmt.Errorf("Duplicated replacement name %s", newName)
340		}
341		usedNames[oldName] = struct{}{}
342		usedNames[newName] = struct{}{}
343		m.oldNameToNewName[oldName] = newName
344	}
345	return nil
346}
347
348func (m *replacements) Get() interface{} {
349	//TODO(dacek): Remove Get() method from interface as it seems unused.
350	return m.oldNameToNewName
351}
352
353func (m *replacements) size() (length int) {
354	return len(m.oldNameToNewName)
355}
356
357type identSet struct {
358	idents []string
359	all    bool
360}
361
362func (m *identSet) String() string {
363	return strings.Join(m.idents, ",")
364}
365func (m *identSet) Set(s string) error {
366	m.idents = strings.FieldsFunc(s, func(c rune) bool {
367		return unicode.IsSpace(c) || c == ','
368	})
369	if len(m.idents) == 1 && m.idents[0] == "*" {
370		m.all = true
371	}
372	return nil
373}
374func (m *identSet) Get() interface{} {
375	return m.idents
376}
377
378type qualifiedProperties struct {
379	properties []qualifiedProperty
380}
381
382type qualifiedProperty struct {
383	parts []string
384}
385
386var _ flag.Getter = (*qualifiedProperties)(nil)
387
388func (p *qualifiedProperty) name() string {
389	return p.parts[len(p.parts)-1]
390}
391func (p *qualifiedProperty) prefixes() []string {
392	return p.parts[:len(p.parts)-1]
393}
394func (p *qualifiedProperty) String() string {
395	return strings.Join(p.parts, ".")
396}
397
398func parseQualifiedProperty(s string) (*qualifiedProperty, error) {
399	parts := strings.Split(s, ".")
400	if len(parts) == 0 {
401		return nil, fmt.Errorf("%q is not a valid property name", s)
402	}
403	for _, part := range parts {
404		if part == "" {
405			return nil, fmt.Errorf("%q is not a valid property name", s)
406		}
407	}
408	prop := qualifiedProperty{parts}
409	return &prop, nil
410
411}
412
413func (p *qualifiedProperties) Set(s string) error {
414	properties := strings.Split(s, ",")
415	if len(properties) == 0 {
416		return fmt.Errorf("%q is not a valid property name", s)
417	}
418
419	p.properties = make([]qualifiedProperty, len(properties))
420	for i := 0; i < len(properties); i++ {
421		tmp, err := parseQualifiedProperty(properties[i])
422		if err != nil {
423			return err
424		}
425		p.properties[i] = *tmp
426	}
427	return nil
428}
429
430func (p *qualifiedProperties) String() string {
431	arrayLength := len(p.properties)
432	props := make([]string, arrayLength)
433	for i := 0; i < len(p.properties); i++ {
434		props[i] = p.properties[i].String()
435	}
436	return strings.Join(props, ",")
437}
438func (p *qualifiedProperties) Get() interface{} {
439	return p.properties
440}
441