• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"bytes"
10	"flag"
11	"fmt"
12	"io"
13	"io/ioutil"
14	"os"
15	"os/exec"
16	"path/filepath"
17	"strings"
18	"unicode"
19
20	"github.com/google/blueprint/parser"
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	targetedProperty = new(qualifiedProperty)
31	addIdents        = new(identSet)
32	removeIdents     = new(identSet)
33	removeProperty   = flag.Bool("remove-property", false, "remove the property")
34	setString        *string
35	addLiteral       *string
36)
37
38func init() {
39	flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
40	flag.Var(targetedProperty, "parameter", "alias to -property=`name`")
41	flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")")
42	flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
43	flag.Var(stringPtrFlag{&addLiteral}, "add-literal", "a literal to add")
44	flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
45	flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
46	flag.Usage = usage
47}
48
49var (
50	exitCode = 0
51)
52
53func report(err error) {
54	fmt.Fprintln(os.Stderr, err)
55	exitCode = 2
56}
57
58func usage() {
59	fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [path ...]\n", os.Args[0])
60	flag.PrintDefaults()
61}
62
63// If in == nil, the source is the contents of the file with the given filename.
64func processFile(filename string, in io.Reader, out io.Writer) error {
65	if in == nil {
66		f, err := os.Open(filename)
67		if err != nil {
68			return err
69		}
70		defer f.Close()
71		in = f
72	}
73
74	src, err := ioutil.ReadAll(in)
75	if err != nil {
76		return err
77	}
78
79	r := bytes.NewBuffer(src)
80
81	file, errs := parser.Parse(filename, r, parser.NewScope(nil))
82	if len(errs) > 0 {
83		for _, err := range errs {
84			fmt.Fprintln(os.Stderr, err)
85		}
86		return fmt.Errorf("%d parsing errors", len(errs))
87	}
88
89	modified, errs := findModules(file)
90	if len(errs) > 0 {
91		for _, err := range errs {
92			fmt.Fprintln(os.Stderr, err)
93		}
94		fmt.Fprintln(os.Stderr, "continuing...")
95	}
96
97	if modified {
98		res, err := parser.Print(file)
99		if err != nil {
100			return err
101		}
102
103		if *list {
104			fmt.Fprintln(out, filename)
105		}
106		if *write {
107			err = ioutil.WriteFile(filename, res, 0644)
108			if err != nil {
109				return err
110			}
111		}
112		if *doDiff {
113			data, err := diff(src, res)
114			if err != nil {
115				return fmt.Errorf("computing diff: %s", err)
116			}
117			fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
118			out.Write(data)
119		}
120
121		if !*list && !*write && !*doDiff {
122			_, err = out.Write(res)
123		}
124	}
125
126	return err
127}
128
129func findModules(file *parser.File) (modified bool, errs []error) {
130
131	for _, def := range file.Defs {
132		if module, ok := def.(*parser.Module); ok {
133			for _, prop := range module.Properties {
134				if prop.Name == "name" && prop.Value.Type() == parser.StringType {
135					if targetedModule(prop.Value.Eval().(*parser.String).Value) {
136						m, newErrs := processModule(module, prop.Name, file)
137						errs = append(errs, newErrs...)
138						modified = modified || m
139					}
140				}
141			}
142		}
143	}
144
145	return modified, errs
146}
147
148func processModule(module *parser.Module, moduleName string,
149	file *parser.File) (modified bool, errs []error) {
150	prop, parent, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
151	if err != nil {
152		return false, []error{err}
153	}
154	if prop == nil {
155		if len(addIdents.idents) > 0 || addLiteral != nil {
156			// We are adding something to a non-existing list prop, so we need to create it first.
157			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{})
158		} else if setString != nil {
159			// We setting a non-existent string property, so we need to create it first.
160			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{})
161		} else {
162			// We cannot find an existing prop, and we aren't adding anything to the prop,
163			// which means we must be removing something from a non-existing prop,
164			// which means this is a noop.
165			return false, nil
166		}
167		if err != nil {
168			// Here should be unreachable, but still handle it for completeness.
169			return false, []error{err}
170		}
171	} else if *removeProperty {
172		// remove-property is used solely, so return here.
173		return parent.RemoveProperty(prop.Name), nil
174	}
175	m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
176	modified = modified || m
177	return modified, errs
178}
179
180func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, parent *parser.Map, err error) {
181	prop, parent, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
182	return prop, parent, err
183}
184
185func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
186	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
187	prop, _, modified, err = getOrCreateRecursiveProperty(module, name, prefixes, empty)
188	return prop, modified, err
189}
190
191func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
192	empty parser.Expression) (prop *parser.Property, parent *parser.Map, modified bool, err error) {
193	m := &module.Map
194	for i, prefix := range prefixes {
195		if prop, found := m.GetProperty(prefix); found {
196			if mm, ok := prop.Value.Eval().(*parser.Map); ok {
197				m = mm
198			} else {
199				// We've found a property in the AST and such property is not of type
200				// *parser.Map, which must mean we didn't modify the AST.
201				return nil, nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
202					strings.Join(prefixes[:i+1], "."), prop.Value.Type())
203			}
204		} else if empty != nil {
205			mm := &parser.Map{}
206			m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm})
207			m = mm
208			// We've created a new node in the AST. This means the m.GetProperty(name)
209			// check after this for loop must fail, because the node we inserted is an
210			// empty parser.Map, thus this function will return |modified| is true.
211		} else {
212			return nil, nil, false, nil
213		}
214	}
215	if prop, found := m.GetProperty(name); found {
216		// We've found a property in the AST, which must mean we didn't modify the AST.
217		return prop, m, false, nil
218	} else if empty != nil {
219		prop = &parser.Property{Name: name, Value: empty}
220		m.Properties = append(m.Properties, prop)
221		return prop, m, true, nil
222	} else {
223		return nil, nil, false, nil
224	}
225}
226
227func processParameter(value parser.Expression, paramName, moduleName string,
228	file *parser.File) (modified bool, errs []error) {
229	if _, ok := value.(*parser.Variable); ok {
230		return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
231			paramName, moduleName)}
232	}
233
234	if _, ok := value.(*parser.Operator); ok {
235		return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
236			paramName, moduleName)}
237	}
238
239	if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
240		list, ok := value.(*parser.List)
241		if !ok {
242			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
243				paramName, moduleName, value.Type().String())}
244		}
245
246		wasSorted := parser.ListIsSorted(list)
247
248		for _, a := range addIdents.idents {
249			m := parser.AddStringToList(list, a)
250			modified = modified || m
251		}
252
253		for _, r := range removeIdents.idents {
254			m := parser.RemoveStringFromList(list, r)
255			modified = modified || m
256		}
257
258		if (wasSorted || *sortLists) && modified {
259			parser.SortList(file, list)
260		}
261	} else if addLiteral != nil {
262		if *sortLists {
263			return false, []error{fmt.Errorf("sorting not supported when adding a literal")}
264		}
265		list, ok := value.(*parser.List)
266		if !ok {
267			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
268				paramName, moduleName, value.Type().String())}
269		}
270		value, errs := parser.ParseExpression(strings.NewReader(*addLiteral))
271		if errs != nil {
272			return false, errs
273		}
274		list.Values = append(list.Values, value)
275		modified = true
276	} else if setString != nil {
277		str, ok := value.(*parser.String)
278		if !ok {
279			return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
280				paramName, moduleName, value.Type().String())}
281		}
282
283		str.Value = *setString
284		modified = true
285	}
286
287	return modified, nil
288}
289
290func targetedModule(name string) bool {
291	if targetedModules.all {
292		return true
293	}
294	for _, m := range targetedModules.idents {
295		if m == name {
296			return true
297		}
298	}
299
300	return false
301}
302
303func visitFile(path string, f os.FileInfo, err error) error {
304	if err == nil && f.Name() == "Blueprints" {
305		err = processFile(path, nil, os.Stdout)
306	}
307	if err != nil {
308		report(err)
309	}
310	return nil
311}
312
313func walkDir(path string) {
314	filepath.Walk(path, visitFile)
315}
316
317func main() {
318	defer func() {
319		if err := recover(); err != nil {
320			report(fmt.Errorf("error: %s", err))
321		}
322		os.Exit(exitCode)
323	}()
324
325	flag.Parse()
326
327	if len(targetedProperty.parts) == 0 {
328		targetedProperty.Set("deps")
329	}
330
331	if flag.NArg() == 0 {
332		if *write {
333			report(fmt.Errorf("error: cannot use -w with standard input"))
334			return
335		}
336		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
337			report(err)
338		}
339		return
340	}
341
342	if len(targetedModules.idents) == 0 {
343		report(fmt.Errorf("-m parameter is required"))
344		return
345	}
346
347	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty {
348		report(fmt.Errorf("-a, -add-literal, -r, -remove-property or -str parameter is required"))
349		return
350	}
351
352	if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil) {
353		report(fmt.Errorf("-remove-property cannot be used with other parameter(s)"))
354		return
355	}
356
357	for i := 0; i < flag.NArg(); i++ {
358		path := flag.Arg(i)
359		switch dir, err := os.Stat(path); {
360		case err != nil:
361			report(err)
362		case dir.IsDir():
363			walkDir(path)
364		default:
365			if err := processFile(path, nil, os.Stdout); err != nil {
366				report(err)
367			}
368		}
369	}
370}
371
372func diff(b1, b2 []byte) (data []byte, err error) {
373	f1, err := ioutil.TempFile("", "bpfmt")
374	if err != nil {
375		return
376	}
377	defer os.Remove(f1.Name())
378	defer f1.Close()
379
380	f2, err := ioutil.TempFile("", "bpfmt")
381	if err != nil {
382		return
383	}
384	defer os.Remove(f2.Name())
385	defer f2.Close()
386
387	f1.Write(b1)
388	f2.Write(b2)
389
390	data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
391	if len(data) > 0 {
392		// diff exits with a non-zero status when the files don't match.
393		// Ignore that failure as long as we get output.
394		err = nil
395	}
396	return
397
398}
399
400type stringPtrFlag struct {
401	s **string
402}
403
404func (f stringPtrFlag) Set(s string) error {
405	*f.s = &s
406	return nil
407}
408
409func (f stringPtrFlag) String() string {
410	if f.s == nil || *f.s == nil {
411		return ""
412	}
413	return **f.s
414}
415
416type identSet struct {
417	idents []string
418	all    bool
419}
420
421func (m *identSet) String() string {
422	return strings.Join(m.idents, ",")
423}
424
425func (m *identSet) Set(s string) error {
426	m.idents = strings.FieldsFunc(s, func(c rune) bool {
427		return unicode.IsSpace(c) || c == ','
428	})
429	if len(m.idents) == 1 && m.idents[0] == "*" {
430		m.all = true
431	}
432	return nil
433}
434
435func (m *identSet) Get() interface{} {
436	return m.idents
437}
438
439type qualifiedProperty struct {
440	parts []string
441}
442
443var _ flag.Getter = (*qualifiedProperty)(nil)
444
445func (p *qualifiedProperty) name() string {
446	return p.parts[len(p.parts)-1]
447}
448
449func (p *qualifiedProperty) prefixes() []string {
450	return p.parts[:len(p.parts)-1]
451}
452
453func (p *qualifiedProperty) String() string {
454	return strings.Join(p.parts, ".")
455}
456
457func (p *qualifiedProperty) Set(s string) error {
458	p.parts = strings.Split(s, ".")
459	if len(p.parts) == 0 {
460		return fmt.Errorf("%q is not a valid property name", s)
461	}
462	for _, part := range p.parts {
463		if part == "" {
464			return fmt.Errorf("%q is not a valid property name", s)
465		}
466	}
467	return nil
468}
469
470func (p *qualifiedProperty) Get() interface{} {
471	return p.parts
472}
473