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