• 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.
5package main
6
7import (
8	"bytes"
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/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	targetedProperties = new(qualifiedProperties)
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
70// If in == nil, the source is the contents of the file with the given filename.
71func processFile(filename string, in io.Reader, out io.Writer) error {
72	if in == nil {
73		f, err := os.Open(filename)
74		if err != nil {
75			return err
76		}
77		defer f.Close()
78		if *write {
79			syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
80		}
81		in = f
82	}
83	src, err := ioutil.ReadAll(in)
84	if err != nil {
85		return err
86	}
87	r := bytes.NewBuffer(src)
88	file, errs := parser.Parse(filename, r, parser.NewScope(nil))
89	if len(errs) > 0 {
90		for _, err := range errs {
91			fmt.Fprintln(os.Stderr, err)
92		}
93		return fmt.Errorf("%d parsing errors", len(errs))
94	}
95	modified, errs := findModules(file)
96	if len(errs) > 0 {
97		for _, err := range errs {
98			fmt.Fprintln(os.Stderr, err)
99		}
100		fmt.Fprintln(os.Stderr, "continuing...")
101	}
102	if modified {
103		res, err := parser.Print(file)
104		if err != nil {
105			return err
106		}
107		if *list {
108			fmt.Fprintln(out, filename)
109		}
110		if *write {
111			err = ioutil.WriteFile(filename, res, 0644)
112			if err != nil {
113				return err
114			}
115		}
116		if *doDiff {
117			data, err := diff(src, res)
118			if err != nil {
119				return fmt.Errorf("computing diff: %s", err)
120			}
121			fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
122			out.Write(data)
123		}
124		if !*list && !*write && !*doDiff {
125			_, err = out.Write(res)
126		}
127	}
128	return err
129}
130func findModules(file *parser.File) (modified bool, errs []error) {
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 && targetedModule(prop.Value.Eval().(*parser.String).Value) {
135					for _, p := range targetedProperties.properties {
136						m, newErrs := processModuleProperty(module, prop.Name, file, p)
137						errs = append(errs, newErrs...)
138						modified = modified || m
139					}
140				}
141			}
142		}
143	}
144	return modified, errs
145}
146
147func processModuleProperty(module *parser.Module, moduleName string,
148	file *parser.File, property qualifiedProperty) (modified bool, errs []error) {
149	prop, parent, err := getRecursiveProperty(module, property.name(), property.prefixes())
150	if err != nil {
151		return false, []error{err}
152	}
153	if prop == nil {
154		if len(addIdents.idents) > 0 || addLiteral != nil {
155			// We are adding something to a non-existing list prop, so we need to create it first.
156			prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.List{})
157		} else if setString != nil {
158			// We setting a non-existent string property, so we need to create it first.
159			prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.String{})
160		} else if setBool != nil {
161			// We are setting a non-existent property, so we need to create it first.
162			prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.Bool{})
163		} else {
164			// We cannot find an existing prop, and we aren't adding anything to the prop,
165			// which means we must be removing something from a non-existing prop,
166			// which means this is a noop.
167			return false, nil
168		}
169		if err != nil {
170			// Here should be unreachable, but still handle it for completeness.
171			return false, []error{err}
172		}
173	} else if *removeProperty {
174		// remove-property is used solely, so return here.
175		return parent.RemoveProperty(prop.Name), nil
176	} else if *moveProperty {
177		return parent.MovePropertyContents(prop.Name, newLocation), nil
178	}
179	m, errs := processParameter(prop.Value, property.String(), moduleName, file)
180	modified = modified || m
181	return modified, errs
182}
183func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, parent *parser.Map, err error) {
184	prop, parent, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
185	return prop, parent, err
186}
187func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
188	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
189	prop, _, modified, err = getOrCreateRecursiveProperty(module, name, prefixes, empty)
190	return prop, modified, err
191}
192func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
193	empty parser.Expression) (prop *parser.Property, parent *parser.Map, modified bool, err error) {
194	m := &module.Map
195	for i, prefix := range prefixes {
196		if prop, found := m.GetProperty(prefix); found {
197			if mm, ok := prop.Value.Eval().(*parser.Map); ok {
198				m = mm
199			} else {
200				// We've found a property in the AST and such property is not of type
201				// *parser.Map, which must mean we didn't modify the AST.
202				return nil, nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
203					strings.Join(prefixes[:i+1], "."), prop.Value.Type())
204			}
205		} else if empty != nil {
206			mm := &parser.Map{}
207			m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm})
208			m = mm
209			// We've created a new node in the AST. This means the m.GetProperty(name)
210			// check after this for loop must fail, because the node we inserted is an
211			// empty parser.Map, thus this function will return |modified| is true.
212		} else {
213			return nil, nil, false, nil
214		}
215	}
216	if prop, found := m.GetProperty(name); found {
217		// We've found a property in the AST, which must mean we didn't modify the AST.
218		return prop, m, false, nil
219	} else if empty != nil {
220		prop = &parser.Property{Name: name, Value: empty}
221		m.Properties = append(m.Properties, prop)
222		return prop, m, true, nil
223	} else {
224		return nil, nil, false, nil
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	if _, ok := value.(*parser.Operator); ok {
234		return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
235			paramName, moduleName)}
236	}
237
238	if (*replaceProperty).size() != 0 {
239		if list, ok := value.Eval().(*parser.List); ok {
240			return parser.ReplaceStringsInList(list, (*replaceProperty).oldNameToNewName), nil
241		} else if str, ok := value.Eval().(*parser.String); ok {
242			oldVal := str.Value
243			replacementValue := (*replaceProperty).oldNameToNewName[oldVal]
244			if replacementValue != "" {
245				str.Value = replacementValue
246				return true, nil
247			} else {
248				return false, nil
249			}
250		}
251		return false, []error{fmt.Errorf("expected parameter %s in module %s to be a list or string, found %s",
252			paramName, moduleName, value.Type().String())}
253	}
254	if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
255		list, ok := value.(*parser.List)
256		if !ok {
257			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
258				paramName, moduleName, value.Type())}
259		}
260		wasSorted := parser.ListIsSorted(list)
261		for _, a := range addIdents.idents {
262			m := parser.AddStringToList(list, a)
263			modified = modified || m
264		}
265		for _, r := range removeIdents.idents {
266			m := parser.RemoveStringFromList(list, r)
267			modified = modified || m
268		}
269		if (wasSorted || *sortLists) && modified {
270			parser.SortList(file, list)
271		}
272	} else if addLiteral != nil {
273		if *sortLists {
274			return false, []error{fmt.Errorf("sorting not supported when adding a literal")}
275		}
276		list, ok := value.(*parser.List)
277		if !ok {
278			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
279				paramName, moduleName, value.Type().String())}
280		}
281		value, errs := parser.ParseExpression(strings.NewReader(*addLiteral))
282		if errs != nil {
283			return false, errs
284		}
285		list.Values = append(list.Values, value)
286		modified = true
287	} else if setBool != nil {
288		res, ok := value.(*parser.Bool)
289		if !ok {
290			return false, []error{fmt.Errorf("expected parameter %s in module %s to be bool, found %s",
291				paramName, moduleName, value.Type().String())}
292		}
293		if *setBool == "true" {
294			res.Value = true
295		} else if *setBool == "false" {
296			res.Value = false
297		} else {
298			return false, []error{fmt.Errorf("expected parameter %s to be true or false, found %s",
299				paramName, *setBool)}
300		}
301		modified = true
302	} else if setString != nil {
303		str, ok := value.(*parser.String)
304		if !ok {
305			return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
306				paramName, moduleName, value.Type().String())}
307		}
308		str.Value = *setString
309		modified = true
310	}
311	return modified, nil
312}
313func targetedModule(name string) bool {
314	if targetedModules.all {
315		return true
316	}
317	for _, m := range targetedModules.idents {
318		if m == name {
319			return true
320		}
321	}
322	return false
323}
324func visitFile(path string, f os.FileInfo, err error) error {
325	//TODO(dacek): figure out a better way to target intended .bp files without parsing errors
326	if err == nil && (f.Name() == "Blueprints" || strings.HasSuffix(f.Name(), ".bp")) {
327		err = processFile(path, nil, os.Stdout)
328	}
329	if err != nil {
330		report(err)
331	}
332	return nil
333}
334func walkDir(path string) {
335	filepath.Walk(path, visitFile)
336}
337func main() {
338	defer func() {
339		if err := recover(); err != nil {
340			report(fmt.Errorf("error: %s", err))
341		}
342		os.Exit(exitCode)
343	}()
344	flag.Parse()
345
346	if len(targetedProperties.properties) == 0 && *moveProperty {
347		report(fmt.Errorf("-move-property must specify property"))
348		return
349	}
350
351	if len(targetedProperties.properties) == 0 {
352		targetedProperties.Set("deps")
353	}
354	if flag.NArg() == 0 {
355		if *write {
356			report(fmt.Errorf("error: cannot use -w with standard input"))
357			return
358		}
359		if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
360			report(err)
361		}
362		return
363	}
364	if len(targetedModules.idents) == 0 {
365		report(fmt.Errorf("-m parameter is required"))
366		return
367	}
368
369	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty && !*moveProperty && (*replaceProperty).size() == 0 && setBool == nil {
370		report(fmt.Errorf("-a, -add-literal, -r, -remove-property, -move-property, replace-property or -str parameter is required"))
371		return
372	}
373	if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
374		report(fmt.Errorf("-remove-property cannot be used with other parameter(s)"))
375		return
376	}
377	if *moveProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
378		report(fmt.Errorf("-move-property cannot be used with other parameter(s)"))
379		return
380	}
381	if *moveProperty && newLocation == "" {
382		report(fmt.Errorf("-move-property must specify -new-location"))
383		return
384	}
385	for i := 0; i < flag.NArg(); i++ {
386		path := flag.Arg(i)
387		switch dir, err := os.Stat(path); {
388		case err != nil:
389			report(err)
390		case dir.IsDir():
391			walkDir(path)
392		default:
393			if err := processFile(path, nil, os.Stdout); err != nil {
394				report(err)
395			}
396		}
397	}
398}
399
400func diff(b1, b2 []byte) (data []byte, err error) {
401	f1, err := ioutil.TempFile("", "bpfmt")
402	if err != nil {
403		return
404	}
405	defer os.Remove(f1.Name())
406	defer f1.Close()
407	f2, err := ioutil.TempFile("", "bpfmt")
408	if err != nil {
409		return
410	}
411	defer os.Remove(f2.Name())
412	defer f2.Close()
413	f1.Write(b1)
414	f2.Write(b2)
415	data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
416	if len(data) > 0 {
417		// diff exits with a non-zero status when the files don't match.
418		// Ignore that failure as long as we get output.
419		err = nil
420	}
421	return
422}
423
424type stringPtrFlag struct {
425	s **string
426}
427
428func (f stringPtrFlag) Set(s string) error {
429	*f.s = &s
430	return nil
431}
432func (f stringPtrFlag) String() string {
433	if f.s == nil || *f.s == nil {
434		return ""
435	}
436	return **f.s
437}
438
439type replacements struct {
440	oldNameToNewName map[string]string
441}
442
443func (m *replacements) String() string {
444	ret := ""
445	sep := ""
446	for k, v := range m.oldNameToNewName {
447		ret += sep
448		ret += k
449		ret += ":"
450		ret += v
451		sep = ","
452	}
453	return ret
454}
455
456func (m *replacements) Set(s string) error {
457	usedNames := make(map[string]struct{})
458
459	pairs := strings.Split(s, ",")
460	length := len(pairs)
461	m.oldNameToNewName = make(map[string]string)
462	for i := 0; i < length; i++ {
463
464		pair := strings.SplitN(pairs[i], "=", 2)
465		if len(pair) != 2 {
466			return fmt.Errorf("Invalid replacement pair %s", pairs[i])
467		}
468		oldName := pair[0]
469		newName := pair[1]
470		if _, seen := usedNames[oldName]; seen {
471			return fmt.Errorf("Duplicated replacement name %s", oldName)
472		}
473		if _, seen := usedNames[newName]; seen {
474			return fmt.Errorf("Duplicated replacement name %s", newName)
475		}
476		usedNames[oldName] = struct{}{}
477		usedNames[newName] = struct{}{}
478		m.oldNameToNewName[oldName] = newName
479	}
480	return nil
481}
482
483func (m *replacements) Get() interface{} {
484	//TODO(dacek): Remove Get() method from interface as it seems unused.
485	return m.oldNameToNewName
486}
487
488func (m *replacements) size() (length int) {
489	return len(m.oldNameToNewName)
490}
491
492type identSet struct {
493	idents []string
494	all    bool
495}
496
497func (m *identSet) String() string {
498	return strings.Join(m.idents, ",")
499}
500func (m *identSet) Set(s string) error {
501	m.idents = strings.FieldsFunc(s, func(c rune) bool {
502		return unicode.IsSpace(c) || c == ','
503	})
504	if len(m.idents) == 1 && m.idents[0] == "*" {
505		m.all = true
506	}
507	return nil
508}
509func (m *identSet) Get() interface{} {
510	return m.idents
511}
512
513type qualifiedProperties struct {
514	properties []qualifiedProperty
515}
516
517type qualifiedProperty struct {
518	parts []string
519}
520
521var _ flag.Getter = (*qualifiedProperties)(nil)
522
523func (p *qualifiedProperty) name() string {
524	return p.parts[len(p.parts)-1]
525}
526func (p *qualifiedProperty) prefixes() []string {
527	return p.parts[:len(p.parts)-1]
528}
529func (p *qualifiedProperty) String() string {
530	return strings.Join(p.parts, ".")
531}
532
533func parseQualifiedProperty(s string) (*qualifiedProperty, error) {
534	parts := strings.Split(s, ".")
535	if len(parts) == 0 {
536		return nil, fmt.Errorf("%q is not a valid property name", s)
537	}
538	for _, part := range parts {
539		if part == "" {
540			return nil, fmt.Errorf("%q is not a valid property name", s)
541		}
542	}
543	prop := qualifiedProperty{parts}
544	return &prop, nil
545
546}
547
548func (p *qualifiedProperties) Set(s string) error {
549	properties := strings.Split(s, ",")
550	if len(properties) == 0 {
551		return fmt.Errorf("%q is not a valid property name", s)
552	}
553
554	p.properties = make([]qualifiedProperty, len(properties))
555	for i := 0; i < len(properties); i++ {
556		tmp, err := parseQualifiedProperty(properties[i])
557		if err != nil {
558			return err
559		}
560		p.properties[i] = *tmp
561	}
562	return nil
563}
564
565func (p *qualifiedProperties) String() string {
566	arrayLength := len(p.properties)
567	props := make([]string, arrayLength)
568	for i := 0; i < len(p.properties); i++ {
569		props[i] = p.properties[i].String()
570	}
571	return strings.Join(props, ",")
572}
573func (p *qualifiedProperties) Get() interface{} {
574	return p.properties
575}
576