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