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