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