• 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	parameter       = flag.String("parameter", "deps", "name of parameter to modify on each module")
30	targetedModules = new(identSet)
31	addIdents       = new(identSet)
32	removeIdents    = new(identSet)
33)
34
35func init() {
36	flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
37	flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
38	flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
39}
40
41var (
42	exitCode = 0
43)
44
45func report(err error) {
46	fmt.Fprintln(os.Stderr, err)
47	exitCode = 2
48}
49
50func usage() {
51	fmt.Fprintln(os.Stderr, "usage: bpmodify [flags] [path ...]")
52	flag.PrintDefaults()
53	os.Exit(2)
54}
55
56// If in == nil, the source is the contents of the file with the given filename.
57func processFile(filename string, in io.Reader, out io.Writer) error {
58	if in == nil {
59		f, err := os.Open(filename)
60		if err != nil {
61			return err
62		}
63		defer f.Close()
64		in = f
65	}
66
67	src, err := ioutil.ReadAll(in)
68	if err != nil {
69		return err
70	}
71
72	r := bytes.NewBuffer(src)
73
74	file, errs := parser.Parse(filename, r, parser.NewScope(nil))
75	if len(errs) > 0 {
76		for _, err := range errs {
77			fmt.Fprintln(os.Stderr, err)
78		}
79		return fmt.Errorf("%d parsing errors", len(errs))
80	}
81
82	modified, errs := findModules(file)
83	if len(errs) > 0 {
84		for _, err := range errs {
85			fmt.Fprintln(os.Stderr, err)
86		}
87		fmt.Fprintln(os.Stderr, "continuing...")
88	}
89
90	if modified {
91		res, err := parser.Print(file)
92		if err != nil {
93			return err
94		}
95
96		if *list {
97			fmt.Fprintln(out, filename)
98		}
99		if *write {
100			err = ioutil.WriteFile(filename, res, 0644)
101			if err != nil {
102				return err
103			}
104		}
105		if *doDiff {
106			data, err := diff(src, res)
107			if err != nil {
108				return fmt.Errorf("computing diff: %s", err)
109			}
110			fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
111			out.Write(data)
112		}
113
114		if !*list && !*write && !*doDiff {
115			_, err = out.Write(res)
116		}
117	}
118
119	return err
120}
121
122func findModules(file *parser.File) (modified bool, errs []error) {
123
124	for _, def := range file.Defs {
125		if module, ok := def.(*parser.Module); ok {
126			for _, prop := range module.Properties {
127				if prop.Name == "name" && prop.Value.Type() == parser.StringType {
128					if targetedModule(prop.Value.Eval().(*parser.String).Value) {
129						m, newErrs := processModule(module, prop.Name, file)
130						errs = append(errs, newErrs...)
131						modified = modified || m
132					}
133				}
134			}
135		}
136	}
137
138	return modified, errs
139}
140
141func processModule(module *parser.Module, moduleName string,
142	file *parser.File) (modified bool, errs []error) {
143
144	for _, prop := range module.Properties {
145		if prop.Name == *parameter {
146			modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
147			return
148		}
149	}
150
151	prop := parser.Property{Name: *parameter, Value: &parser.List{}}
152	modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
153
154	if modified {
155		module.Properties = append(module.Properties, &prop)
156	}
157
158	return modified, errs
159}
160
161func processParameter(value parser.Expression, paramName, moduleName string,
162	file *parser.File) (modified bool, errs []error) {
163	if _, ok := value.(*parser.Variable); ok {
164		return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
165			paramName, moduleName)}
166	}
167
168	if _, ok := value.(*parser.Operator); ok {
169		return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
170			paramName, moduleName)}
171	}
172
173	list, ok := value.(*parser.List)
174	if !ok {
175		return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
176			paramName, moduleName, value.Type().String())}
177	}
178
179	wasSorted := parser.ListIsSorted(list)
180
181	for _, a := range addIdents.idents {
182		m := parser.AddStringToList(list, a)
183		modified = modified || m
184	}
185
186	for _, r := range removeIdents.idents {
187		m := parser.RemoveStringFromList(list, r)
188		modified = modified || m
189	}
190
191	if (wasSorted || *sortLists) && modified {
192		parser.SortList(file, list)
193	}
194
195	return modified, nil
196}
197
198func targetedModule(name string) bool {
199	if targetedModules.all {
200		return true
201	}
202	for _, m := range targetedModules.idents {
203		if m == name {
204			return true
205		}
206	}
207
208	return false
209}
210
211func visitFile(path string, f os.FileInfo, err error) error {
212	if err == nil && f.Name() == "Blueprints" {
213		err = processFile(path, nil, os.Stdout)
214	}
215	if err != nil {
216		report(err)
217	}
218	return nil
219}
220
221func walkDir(path string) {
222	filepath.Walk(path, visitFile)
223}
224
225func main() {
226	flag.Parse()
227
228	if flag.NArg() == 0 {
229		if *write {
230			fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
231			exitCode = 2
232			return
233		}
234		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
235			report(err)
236		}
237		return
238	}
239
240	if len(targetedModules.idents) == 0 {
241		report(fmt.Errorf("-m parameter is required"))
242		return
243	}
244
245	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 {
246		report(fmt.Errorf("-a or -r parameter is required"))
247		return
248	}
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
273	f2, err := ioutil.TempFile("", "bpfmt")
274	if err != nil {
275		return
276	}
277	defer os.Remove(f2.Name())
278	defer f2.Close()
279
280	f1.Write(b1)
281	f2.Write(b2)
282
283	data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
284	if len(data) > 0 {
285		// diff exits with a non-zero status when the files don't match.
286		// Ignore that failure as long as we get output.
287		err = nil
288	}
289	return
290
291}
292
293type identSet struct {
294	idents []string
295	all    bool
296}
297
298func (m *identSet) String() string {
299	return strings.Join(m.idents, ",")
300}
301
302func (m *identSet) Set(s string) error {
303	m.idents = strings.FieldsFunc(s, func(c rune) bool {
304		return unicode.IsSpace(c) || c == ','
305	})
306	if len(m.idents) == 1 && m.idents[0] == "*" {
307		m.all = true
308	}
309	return nil
310}
311
312func (m *identSet) Get() interface{} {
313	return m.idents
314}
315