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 "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/bpmodify" 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(identSet) 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 70func processBp(bp *bpmodify.Blueprint) error { 71 var modules *bpmodify.ModuleSet 72 if targetedModules.all { 73 modules = bp.AllModules() 74 } else { 75 modules = bp.ModulesByName(targetedModules.idents...) 76 } 77 78 if *removeProperty { 79 // remove-property is used solely, so return here. 80 return modules.RemoveProperty(targetedProperties.idents...) 81 } else if *moveProperty { 82 return modules.MoveProperty(newLocation, targetedProperties.idents...) 83 } else if len(addIdents.idents) > 0 { 84 props, err := modules.GetOrCreateProperty(bpmodify.List, targetedProperties.idents...) 85 if err != nil { 86 return err 87 } 88 return props.AddStringToList(addIdents.idents...) 89 } else if addLiteral != nil { 90 props, err := modules.GetOrCreateProperty(bpmodify.List, targetedProperties.idents...) 91 if err != nil { 92 return err 93 } 94 return props.AddLiteral(*addLiteral) 95 } else if setString != nil { 96 props, err := modules.GetOrCreateProperty(bpmodify.String, targetedProperties.idents...) 97 if err != nil { 98 return err 99 } 100 return props.SetString(*setString) 101 } else if setBool != nil { 102 props, err := modules.GetOrCreateProperty(bpmodify.Bool, targetedProperties.idents...) 103 if err != nil { 104 return err 105 } 106 var value bool 107 if *setBool == "true" { 108 value = true 109 } else if *setBool == "false" { 110 value = false 111 } else { 112 return fmt.Errorf("expected parameter to be true or false, found %s", *setBool) 113 } 114 return props.SetBool(value) 115 } else { 116 props, err := modules.GetProperty(targetedProperties.idents...) 117 if err != nil { 118 return err 119 } 120 if len(removeIdents.idents) > 0 { 121 return props.RemoveStringFromList(removeIdents.idents...) 122 } else if replaceProperty.size() != 0 { 123 return props.ReplaceStrings(replaceProperty.oldNameToNewName) 124 } 125 } 126 127 return nil 128} 129 130// If in == nil, the source is the contents of the file with the given filename. 131func processFile(filename string, in io.Reader, out io.Writer) error { 132 if in == nil { 133 f, err := os.Open(filename) 134 if err != nil { 135 return err 136 } 137 defer f.Close() 138 if *write { 139 syscall.Flock(int(f.Fd()), syscall.LOCK_EX) 140 } 141 in = f 142 } 143 144 src, err := io.ReadAll(in) 145 if err != nil { 146 return err 147 } 148 149 bp, err := bpmodify.NewBlueprint(filename, src) 150 if err != nil { 151 return err 152 } 153 154 err = processBp(bp) 155 if err != nil { 156 return err 157 } 158 159 res, err := bp.Bytes() 160 if err != nil { 161 return err 162 } 163 if *list { 164 fmt.Fprintln(out, filename) 165 } 166 if *write { 167 err = os.WriteFile(filename, res, 0644) 168 if err != nil { 169 return err 170 } 171 } 172 if *doDiff { 173 data, err := diff(src, res) 174 if err != nil { 175 return fmt.Errorf("computing diff: %s", err) 176 } 177 fmt.Printf("diff %s bpfmt/%s\n", filename, filename) 178 out.Write(data) 179 } 180 if !*list && !*write && !*doDiff { 181 _, err = out.Write(res) 182 } 183 184 return err 185} 186 187func visitFile(path string, f os.FileInfo, err error) error { 188 //TODO(dacek): figure out a better way to target intended .bp files without parsing errors 189 if err == nil && (f.Name() == "Blueprints" || strings.HasSuffix(f.Name(), ".bp")) { 190 err = processFile(path, nil, os.Stdout) 191 } 192 if err != nil { 193 report(err) 194 } 195 return nil 196} 197 198func walkDir(path string) { 199 filepath.Walk(path, visitFile) 200} 201 202func main() { 203 defer func() { 204 if err := recover(); err != nil { 205 report(fmt.Errorf("error: %s", err)) 206 } 207 os.Exit(exitCode) 208 }() 209 flag.Parse() 210 211 if len(targetedProperties.idents) == 0 && *moveProperty { 212 report(fmt.Errorf("-move-property must specify property")) 213 return 214 } 215 216 if len(targetedProperties.idents) == 0 { 217 targetedProperties.Set("deps") 218 } 219 if flag.NArg() == 0 { 220 if *write { 221 report(fmt.Errorf("error: cannot use -w with standard input")) 222 return 223 } 224 if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil { 225 report(err) 226 } 227 return 228 } 229 if len(targetedModules.idents) == 0 { 230 report(fmt.Errorf("-m parameter is required")) 231 return 232 } 233 234 if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty && !*moveProperty && (*replaceProperty).size() == 0 && setBool == nil { 235 report(fmt.Errorf("-a, -add-literal, -r, -remove-property, -move-property, replace-property or -str parameter is required")) 236 return 237 } 238 if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) { 239 report(fmt.Errorf("-remove-property cannot be used with other parameter(s)")) 240 return 241 } 242 if *moveProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) { 243 report(fmt.Errorf("-move-property cannot be used with other parameter(s)")) 244 return 245 } 246 if *moveProperty && newLocation == "" { 247 report(fmt.Errorf("-move-property must specify -new-location")) 248 return 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 f2, err := ioutil.TempFile("", "bpfmt") 273 if err != nil { 274 return 275 } 276 defer os.Remove(f2.Name()) 277 defer f2.Close() 278 f1.Write(b1) 279 f2.Write(b2) 280 data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput() 281 if len(data) > 0 { 282 // diff exits with a non-zero status when the files don't match. 283 // Ignore that failure as long as we get output. 284 err = nil 285 } 286 return 287} 288 289type stringPtrFlag struct { 290 s **string 291} 292 293func (f stringPtrFlag) Set(s string) error { 294 *f.s = &s 295 return nil 296} 297func (f stringPtrFlag) String() string { 298 if f.s == nil || *f.s == nil { 299 return "" 300 } 301 return **f.s 302} 303 304type replacements struct { 305 oldNameToNewName map[string]string 306} 307 308func (m *replacements) String() string { 309 ret := "" 310 sep := "" 311 for k, v := range m.oldNameToNewName { 312 ret += sep 313 ret += k 314 ret += ":" 315 ret += v 316 sep = "," 317 } 318 return ret 319} 320 321func (m *replacements) Set(s string) error { 322 usedNames := make(map[string]struct{}) 323 324 pairs := strings.Split(s, ",") 325 length := len(pairs) 326 m.oldNameToNewName = make(map[string]string) 327 for i := 0; i < length; i++ { 328 329 pair := strings.SplitN(pairs[i], "=", 2) 330 if len(pair) != 2 { 331 return fmt.Errorf("Invalid replacement pair %s", pairs[i]) 332 } 333 oldName := pair[0] 334 newName := pair[1] 335 if _, seen := usedNames[oldName]; seen { 336 return fmt.Errorf("Duplicated replacement name %s", oldName) 337 } 338 if _, seen := usedNames[newName]; seen { 339 return fmt.Errorf("Duplicated replacement name %s", newName) 340 } 341 usedNames[oldName] = struct{}{} 342 usedNames[newName] = struct{}{} 343 m.oldNameToNewName[oldName] = newName 344 } 345 return nil 346} 347 348func (m *replacements) Get() interface{} { 349 //TODO(dacek): Remove Get() method from interface as it seems unused. 350 return m.oldNameToNewName 351} 352 353func (m *replacements) size() (length int) { 354 return len(m.oldNameToNewName) 355} 356 357type identSet struct { 358 idents []string 359 all bool 360} 361 362func (m *identSet) String() string { 363 return strings.Join(m.idents, ",") 364} 365func (m *identSet) Set(s string) error { 366 m.idents = strings.FieldsFunc(s, func(c rune) bool { 367 return unicode.IsSpace(c) || c == ',' 368 }) 369 if len(m.idents) == 1 && m.idents[0] == "*" { 370 m.all = true 371 } 372 return nil 373} 374func (m *identSet) Get() interface{} { 375 return m.idents 376} 377 378type qualifiedProperties struct { 379 properties []qualifiedProperty 380} 381 382type qualifiedProperty struct { 383 parts []string 384} 385 386var _ flag.Getter = (*qualifiedProperties)(nil) 387 388func (p *qualifiedProperty) name() string { 389 return p.parts[len(p.parts)-1] 390} 391func (p *qualifiedProperty) prefixes() []string { 392 return p.parts[:len(p.parts)-1] 393} 394func (p *qualifiedProperty) String() string { 395 return strings.Join(p.parts, ".") 396} 397 398func parseQualifiedProperty(s string) (*qualifiedProperty, error) { 399 parts := strings.Split(s, ".") 400 if len(parts) == 0 { 401 return nil, fmt.Errorf("%q is not a valid property name", s) 402 } 403 for _, part := range parts { 404 if part == "" { 405 return nil, fmt.Errorf("%q is not a valid property name", s) 406 } 407 } 408 prop := qualifiedProperty{parts} 409 return &prop, nil 410 411} 412 413func (p *qualifiedProperties) Set(s string) error { 414 properties := strings.Split(s, ",") 415 if len(properties) == 0 { 416 return fmt.Errorf("%q is not a valid property name", s) 417 } 418 419 p.properties = make([]qualifiedProperty, len(properties)) 420 for i := 0; i < len(properties); i++ { 421 tmp, err := parseQualifiedProperty(properties[i]) 422 if err != nil { 423 return err 424 } 425 p.properties[i] = *tmp 426 } 427 return nil 428} 429 430func (p *qualifiedProperties) String() string { 431 arrayLength := len(p.properties) 432 props := make([]string, arrayLength) 433 for i := 0; i < len(p.properties); i++ { 434 props[i] = p.properties[i].String() 435 } 436 return strings.Join(props, ",") 437} 438func (p *qualifiedProperties) Get() interface{} { 439 return p.properties 440} 441