Home | History | Annotate | Download | only in bpmodify
      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 
      6 package main
      7 
      8 import (
      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 
     22 var (
     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 
     34 func 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 
     40 var (
     41 	exitCode = 0
     42 )
     43 
     44 func report(err error) {
     45 	fmt.Fprintln(os.Stderr, err)
     46 	exitCode = 2
     47 }
     48 
     49 func 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.
     56 func 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 
    121 func 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 
    140 func 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 
    153 func 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 
    189 func 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 
    202 func 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 
    212 func walkDir(path string) {
    213 	filepath.Walk(path, visitFile)
    214 }
    215 
    216 func 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 
    256 func 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 
    284 type identSet struct {
    285 	idents []string
    286 	all    bool
    287 }
    288 
    289 func (m *identSet) String() string {
    290 	return strings.Join(m.idents, ",")
    291 }
    292 
    293 func (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 
    303 func (m *identSet) Get() interface{} {
    304 	return m.idents
    305 }
    306