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 targetedModules = new(identSet) 30 targetedProperty = new(qualifiedProperty) 31 addIdents = new(identSet) 32 removeIdents = new(identSet) 33 removeProperty = flag.Bool("remove-property", false, "remove the property") 34 setString *string 35 addLiteral *string 36) 37 38func init() { 39 flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate") 40 flag.Var(targetedProperty, "parameter", "alias to -property=`name`") 41 flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")") 42 flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add") 43 flag.Var(stringPtrFlag{&addLiteral}, "add-literal", "a literal to add") 44 flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove") 45 flag.Var(stringPtrFlag{&setString}, "str", "set a string property") 46 flag.Usage = usage 47} 48 49var ( 50 exitCode = 0 51) 52 53func report(err error) { 54 fmt.Fprintln(os.Stderr, err) 55 exitCode = 2 56} 57 58func usage() { 59 fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [path ...]\n", os.Args[0]) 60 flag.PrintDefaults() 61} 62 63// If in == nil, the source is the contents of the file with the given filename. 64func processFile(filename string, in io.Reader, out io.Writer) error { 65 if in == nil { 66 f, err := os.Open(filename) 67 if err != nil { 68 return err 69 } 70 defer f.Close() 71 in = f 72 } 73 74 src, err := ioutil.ReadAll(in) 75 if err != nil { 76 return err 77 } 78 79 r := bytes.NewBuffer(src) 80 81 file, errs := parser.Parse(filename, r, parser.NewScope(nil)) 82 if len(errs) > 0 { 83 for _, err := range errs { 84 fmt.Fprintln(os.Stderr, err) 85 } 86 return fmt.Errorf("%d parsing errors", len(errs)) 87 } 88 89 modified, errs := findModules(file) 90 if len(errs) > 0 { 91 for _, err := range errs { 92 fmt.Fprintln(os.Stderr, err) 93 } 94 fmt.Fprintln(os.Stderr, "continuing...") 95 } 96 97 if modified { 98 res, err := parser.Print(file) 99 if err != nil { 100 return err 101 } 102 103 if *list { 104 fmt.Fprintln(out, filename) 105 } 106 if *write { 107 err = ioutil.WriteFile(filename, res, 0644) 108 if err != nil { 109 return err 110 } 111 } 112 if *doDiff { 113 data, err := diff(src, res) 114 if err != nil { 115 return fmt.Errorf("computing diff: %s", err) 116 } 117 fmt.Printf("diff %s bpfmt/%s\n", filename, filename) 118 out.Write(data) 119 } 120 121 if !*list && !*write && !*doDiff { 122 _, err = out.Write(res) 123 } 124 } 125 126 return err 127} 128 129func findModules(file *parser.File) (modified bool, errs []error) { 130 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 { 135 if targetedModule(prop.Value.Eval().(*parser.String).Value) { 136 m, newErrs := processModule(module, prop.Name, file) 137 errs = append(errs, newErrs...) 138 modified = modified || m 139 } 140 } 141 } 142 } 143 } 144 145 return modified, errs 146} 147 148func processModule(module *parser.Module, moduleName string, 149 file *parser.File) (modified bool, errs []error) { 150 prop, parent, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes()) 151 if err != nil { 152 return false, []error{err} 153 } 154 if prop == nil { 155 if len(addIdents.idents) > 0 || addLiteral != nil { 156 // We are adding something to a non-existing list prop, so we need to create it first. 157 prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{}) 158 } else if setString != nil { 159 // We setting a non-existent string property, so we need to create it first. 160 prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{}) 161 } else { 162 // We cannot find an existing prop, and we aren't adding anything to the prop, 163 // which means we must be removing something from a non-existing prop, 164 // which means this is a noop. 165 return false, nil 166 } 167 if err != nil { 168 // Here should be unreachable, but still handle it for completeness. 169 return false, []error{err} 170 } 171 } else if *removeProperty { 172 // remove-property is used solely, so return here. 173 return parent.RemoveProperty(prop.Name), nil 174 } 175 m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file) 176 modified = modified || m 177 return modified, errs 178} 179 180func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, parent *parser.Map, err error) { 181 prop, parent, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil) 182 return prop, parent, err 183} 184 185func createRecursiveProperty(module *parser.Module, name string, prefixes []string, 186 empty parser.Expression) (prop *parser.Property, modified bool, err error) { 187 prop, _, modified, err = getOrCreateRecursiveProperty(module, name, prefixes, empty) 188 return prop, modified, err 189} 190 191func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string, 192 empty parser.Expression) (prop *parser.Property, parent *parser.Map, modified bool, err error) { 193 m := &module.Map 194 for i, prefix := range prefixes { 195 if prop, found := m.GetProperty(prefix); found { 196 if mm, ok := prop.Value.Eval().(*parser.Map); ok { 197 m = mm 198 } else { 199 // We've found a property in the AST and such property is not of type 200 // *parser.Map, which must mean we didn't modify the AST. 201 return nil, nil, false, fmt.Errorf("Expected property %q to be a map, found %s", 202 strings.Join(prefixes[:i+1], "."), prop.Value.Type()) 203 } 204 } else if empty != nil { 205 mm := &parser.Map{} 206 m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm}) 207 m = mm 208 // We've created a new node in the AST. This means the m.GetProperty(name) 209 // check after this for loop must fail, because the node we inserted is an 210 // empty parser.Map, thus this function will return |modified| is true. 211 } else { 212 return nil, nil, false, nil 213 } 214 } 215 if prop, found := m.GetProperty(name); found { 216 // We've found a property in the AST, which must mean we didn't modify the AST. 217 return prop, m, false, nil 218 } else if empty != nil { 219 prop = &parser.Property{Name: name, Value: empty} 220 m.Properties = append(m.Properties, prop) 221 return prop, m, true, nil 222 } else { 223 return nil, nil, false, nil 224 } 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 234 if _, ok := value.(*parser.Operator); ok { 235 return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported", 236 paramName, moduleName)} 237 } 238 239 if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 { 240 list, ok := value.(*parser.List) 241 if !ok { 242 return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s", 243 paramName, moduleName, value.Type().String())} 244 } 245 246 wasSorted := parser.ListIsSorted(list) 247 248 for _, a := range addIdents.idents { 249 m := parser.AddStringToList(list, a) 250 modified = modified || m 251 } 252 253 for _, r := range removeIdents.idents { 254 m := parser.RemoveStringFromList(list, r) 255 modified = modified || m 256 } 257 258 if (wasSorted || *sortLists) && modified { 259 parser.SortList(file, list) 260 } 261 } else if addLiteral != nil { 262 if *sortLists { 263 return false, []error{fmt.Errorf("sorting not supported when adding a literal")} 264 } 265 list, ok := value.(*parser.List) 266 if !ok { 267 return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s", 268 paramName, moduleName, value.Type().String())} 269 } 270 value, errs := parser.ParseExpression(strings.NewReader(*addLiteral)) 271 if errs != nil { 272 return false, errs 273 } 274 list.Values = append(list.Values, value) 275 modified = true 276 } else if setString != nil { 277 str, ok := value.(*parser.String) 278 if !ok { 279 return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s", 280 paramName, moduleName, value.Type().String())} 281 } 282 283 str.Value = *setString 284 modified = true 285 } 286 287 return modified, nil 288} 289 290func targetedModule(name string) bool { 291 if targetedModules.all { 292 return true 293 } 294 for _, m := range targetedModules.idents { 295 if m == name { 296 return true 297 } 298 } 299 300 return false 301} 302 303func visitFile(path string, f os.FileInfo, err error) error { 304 if err == nil && f.Name() == "Blueprints" { 305 err = processFile(path, nil, os.Stdout) 306 } 307 if err != nil { 308 report(err) 309 } 310 return nil 311} 312 313func walkDir(path string) { 314 filepath.Walk(path, visitFile) 315} 316 317func main() { 318 defer func() { 319 if err := recover(); err != nil { 320 report(fmt.Errorf("error: %s", err)) 321 } 322 os.Exit(exitCode) 323 }() 324 325 flag.Parse() 326 327 if len(targetedProperty.parts) == 0 { 328 targetedProperty.Set("deps") 329 } 330 331 if flag.NArg() == 0 { 332 if *write { 333 report(fmt.Errorf("error: cannot use -w with standard input")) 334 return 335 } 336 if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil { 337 report(err) 338 } 339 return 340 } 341 342 if len(targetedModules.idents) == 0 { 343 report(fmt.Errorf("-m parameter is required")) 344 return 345 } 346 347 if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty { 348 report(fmt.Errorf("-a, -add-literal, -r, -remove-property or -str parameter is required")) 349 return 350 } 351 352 if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil) { 353 report(fmt.Errorf("-remove-property cannot be used with other parameter(s)")) 354 return 355 } 356 357 for i := 0; i < flag.NArg(); i++ { 358 path := flag.Arg(i) 359 switch dir, err := os.Stat(path); { 360 case err != nil: 361 report(err) 362 case dir.IsDir(): 363 walkDir(path) 364 default: 365 if err := processFile(path, nil, os.Stdout); err != nil { 366 report(err) 367 } 368 } 369 } 370} 371 372func diff(b1, b2 []byte) (data []byte, err error) { 373 f1, err := ioutil.TempFile("", "bpfmt") 374 if err != nil { 375 return 376 } 377 defer os.Remove(f1.Name()) 378 defer f1.Close() 379 380 f2, err := ioutil.TempFile("", "bpfmt") 381 if err != nil { 382 return 383 } 384 defer os.Remove(f2.Name()) 385 defer f2.Close() 386 387 f1.Write(b1) 388 f2.Write(b2) 389 390 data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput() 391 if len(data) > 0 { 392 // diff exits with a non-zero status when the files don't match. 393 // Ignore that failure as long as we get output. 394 err = nil 395 } 396 return 397 398} 399 400type stringPtrFlag struct { 401 s **string 402} 403 404func (f stringPtrFlag) Set(s string) error { 405 *f.s = &s 406 return nil 407} 408 409func (f stringPtrFlag) String() string { 410 if f.s == nil || *f.s == nil { 411 return "" 412 } 413 return **f.s 414} 415 416type identSet struct { 417 idents []string 418 all bool 419} 420 421func (m *identSet) String() string { 422 return strings.Join(m.idents, ",") 423} 424 425func (m *identSet) Set(s string) error { 426 m.idents = strings.FieldsFunc(s, func(c rune) bool { 427 return unicode.IsSpace(c) || c == ',' 428 }) 429 if len(m.idents) == 1 && m.idents[0] == "*" { 430 m.all = true 431 } 432 return nil 433} 434 435func (m *identSet) Get() interface{} { 436 return m.idents 437} 438 439type qualifiedProperty struct { 440 parts []string 441} 442 443var _ flag.Getter = (*qualifiedProperty)(nil) 444 445func (p *qualifiedProperty) name() string { 446 return p.parts[len(p.parts)-1] 447} 448 449func (p *qualifiedProperty) prefixes() []string { 450 return p.parts[:len(p.parts)-1] 451} 452 453func (p *qualifiedProperty) String() string { 454 return strings.Join(p.parts, ".") 455} 456 457func (p *qualifiedProperty) Set(s string) error { 458 p.parts = strings.Split(s, ".") 459 if len(p.parts) == 0 { 460 return fmt.Errorf("%q is not a valid property name", s) 461 } 462 for _, part := range p.parts { 463 if part == "" { 464 return fmt.Errorf("%q is not a valid property name", s) 465 } 466 } 467 return nil 468} 469 470func (p *qualifiedProperty) Get() interface{} { 471 return p.parts 472} 473