blob: 29d28f09ad06387ea3076d8852d0dafb98b7b23d [file] [log] [blame]
Colin Cross41c397a2015-01-12 17:43:04 -08001// 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 (
Colin Cross41c397a2015-01-12 17:43:04 -08009 "bytes"
10 "flag"
11 "fmt"
Colin Cross41c397a2015-01-12 17:43:04 -080012 "io"
13 "io/ioutil"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "strings"
18 "unicode"
Colin Crosse32cc802016-06-07 12:28:16 -070019
20 "github.com/google/blueprint/parser"
Colin Cross41c397a2015-01-12 17:43:04 -080021)
22
23var (
24 // main operation modes
Yo Chiangb138d492020-03-04 20:53:21 +080025 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)
Colin Crossb1abbad2021-04-29 20:16:40 -070033
34 setString *string
Colin Cross41c397a2015-01-12 17:43:04 -080035)
36
37func init() {
38 flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
Yo Chiangb138d492020-03-04 20:53:21 +080039 flag.Var(targetedProperty, "parameter", "alias to -property=`name`")
40 flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")")
Colin Cross41c397a2015-01-12 17:43:04 -080041 flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
42 flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
Colin Crossb1abbad2021-04-29 20:16:40 -070043 flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
Yo Chiang3edfbc22020-03-03 15:42:52 +080044 flag.Usage = usage
Colin Cross41c397a2015-01-12 17:43:04 -080045}
46
47var (
48 exitCode = 0
49)
50
51func report(err error) {
52 fmt.Fprintln(os.Stderr, err)
53 exitCode = 2
54}
55
56func usage() {
Yo Chiang3edfbc22020-03-03 15:42:52 +080057 fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [path ...]\n", os.Args[0])
Colin Cross41c397a2015-01-12 17:43:04 -080058 flag.PrintDefaults()
Colin Cross41c397a2015-01-12 17:43:04 -080059}
60
61// If in == nil, the source is the contents of the file with the given filename.
62func processFile(filename string, in io.Reader, out io.Writer) error {
63 if in == nil {
64 f, err := os.Open(filename)
65 if err != nil {
66 return err
67 }
68 defer f.Close()
69 in = f
70 }
71
72 src, err := ioutil.ReadAll(in)
73 if err != nil {
74 return err
75 }
76
77 r := bytes.NewBuffer(src)
78
79 file, errs := parser.Parse(filename, r, parser.NewScope(nil))
80 if len(errs) > 0 {
81 for _, err := range errs {
82 fmt.Fprintln(os.Stderr, err)
83 }
84 return fmt.Errorf("%d parsing errors", len(errs))
85 }
86
87 modified, errs := findModules(file)
88 if len(errs) > 0 {
89 for _, err := range errs {
90 fmt.Fprintln(os.Stderr, err)
91 }
92 fmt.Fprintln(os.Stderr, "continuing...")
93 }
94
95 if modified {
96 res, err := parser.Print(file)
97 if err != nil {
98 return err
99 }
100
101 if *list {
102 fmt.Fprintln(out, filename)
103 }
104 if *write {
105 err = ioutil.WriteFile(filename, res, 0644)
106 if err != nil {
107 return err
108 }
109 }
110 if *doDiff {
111 data, err := diff(src, res)
112 if err != nil {
113 return fmt.Errorf("computing diff: %s", err)
114 }
115 fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
116 out.Write(data)
117 }
118
119 if !*list && !*write && !*doDiff {
120 _, err = out.Write(res)
121 }
122 }
123
124 return err
125}
126
127func findModules(file *parser.File) (modified bool, errs []error) {
128
129 for _, def := range file.Defs {
130 if module, ok := def.(*parser.Module); ok {
131 for _, prop := range module.Properties {
Colin Crossc32c4792016-06-09 15:52:30 -0700132 if prop.Name == "name" && prop.Value.Type() == parser.StringType {
Colin Crosse32cc802016-06-07 12:28:16 -0700133 if targetedModule(prop.Value.Eval().(*parser.String).Value) {
Colin Crossc32c4792016-06-09 15:52:30 -0700134 m, newErrs := processModule(module, prop.Name, file)
Colin Cross41c397a2015-01-12 17:43:04 -0800135 errs = append(errs, newErrs...)
136 modified = modified || m
137 }
138 }
139 }
140 }
141 }
142
143 return modified, errs
144}
145
146func processModule(module *parser.Module, moduleName string,
147 file *parser.File) (modified bool, errs []error) {
Yo Chiangb138d492020-03-04 20:53:21 +0800148 prop, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
149 if err != nil {
150 return false, []error{err}
151 }
152 if prop == nil {
Colin Crossb1abbad2021-04-29 20:16:40 -0700153 if len(addIdents.idents) > 0 {
154 // We are adding something to a non-existing list prop, so we need to create it first.
155 prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{})
156 } else if setString != nil {
157 // We setting a non-existent string property, so we need to create it first.
158 prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{})
159 } else {
Yo Chiangb138d492020-03-04 20:53:21 +0800160 // We cannot find an existing prop, and we aren't adding anything to the prop,
161 // which means we must be removing something from a non-existing prop,
162 // which means this is a noop.
163 return false, nil
164 }
Yo Chiangb138d492020-03-04 20:53:21 +0800165 if err != nil {
166 // Here should be unreachable, but still handle it for completeness.
167 return false, []error{err}
Colin Cross41c397a2015-01-12 17:43:04 -0800168 }
169 }
Yo Chiangb138d492020-03-04 20:53:21 +0800170 m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
171 modified = modified || m
Steven Morelandb40aaad2018-12-17 14:36:45 -0800172 return modified, errs
Colin Cross41c397a2015-01-12 17:43:04 -0800173}
174
Yo Chiangb138d492020-03-04 20:53:21 +0800175func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, err error) {
Colin Crossb1abbad2021-04-29 20:16:40 -0700176 prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
Yo Chiangb138d492020-03-04 20:53:21 +0800177 return prop, err
178}
179
Colin Crossb1abbad2021-04-29 20:16:40 -0700180func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
181 empty parser.Expression) (prop *parser.Property, modified bool, err error) {
182
183 return getOrCreateRecursiveProperty(module, name, prefixes, empty)
Yo Chiangb138d492020-03-04 20:53:21 +0800184}
185
186func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
Colin Crossb1abbad2021-04-29 20:16:40 -0700187 empty parser.Expression) (prop *parser.Property, modified bool, err error) {
Yo Chiangb138d492020-03-04 20:53:21 +0800188 m := &module.Map
189 for i, prefix := range prefixes {
190 if prop, found := m.GetProperty(prefix); found {
191 if mm, ok := prop.Value.Eval().(*parser.Map); ok {
192 m = mm
193 } else {
194 // We've found a property in the AST and such property is not of type
195 // *parser.Map, which must mean we didn't modify the AST.
196 return nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
197 strings.Join(prefixes[:i+1], "."), prop.Value.Type())
198 }
Colin Crossb1abbad2021-04-29 20:16:40 -0700199 } else if empty != nil {
Yo Chiangb138d492020-03-04 20:53:21 +0800200 mm := &parser.Map{}
201 m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm})
202 m = mm
203 // We've created a new node in the AST. This means the m.GetProperty(name)
204 // check after this for loop must fail, because the node we inserted is an
205 // empty parser.Map, thus this function will return |modified| is true.
206 } else {
207 return nil, false, nil
208 }
209 }
210 if prop, found := m.GetProperty(name); found {
211 // We've found a property in the AST, which must mean we didn't modify the AST.
212 return prop, false, nil
Colin Crossb1abbad2021-04-29 20:16:40 -0700213 } else if empty != nil {
214 prop = &parser.Property{Name: name, Value: empty}
Yo Chiangb138d492020-03-04 20:53:21 +0800215 m.Properties = append(m.Properties, prop)
216 return prop, true, nil
217 } else {
218 return nil, false, nil
219 }
220}
221
Colin Crosse32cc802016-06-07 12:28:16 -0700222func processParameter(value parser.Expression, paramName, moduleName string,
Colin Cross41c397a2015-01-12 17:43:04 -0800223 file *parser.File) (modified bool, errs []error) {
Colin Crosse32cc802016-06-07 12:28:16 -0700224 if _, ok := value.(*parser.Variable); ok {
Colin Cross41c397a2015-01-12 17:43:04 -0800225 return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
226 paramName, moduleName)}
227 }
228
Colin Crosse32cc802016-06-07 12:28:16 -0700229 if _, ok := value.(*parser.Operator); ok {
Colin Cross41c397a2015-01-12 17:43:04 -0800230 return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
231 paramName, moduleName)}
232 }
233
Colin Crossb1abbad2021-04-29 20:16:40 -0700234 if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
235 list, ok := value.(*parser.List)
236 if !ok {
237 return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
238 paramName, moduleName, value.Type().String())}
239 }
Colin Crosse32cc802016-06-07 12:28:16 -0700240
Colin Crossb1abbad2021-04-29 20:16:40 -0700241 wasSorted := parser.ListIsSorted(list)
Colin Cross41c397a2015-01-12 17:43:04 -0800242
Colin Crossb1abbad2021-04-29 20:16:40 -0700243 for _, a := range addIdents.idents {
244 m := parser.AddStringToList(list, a)
245 modified = modified || m
246 }
Colin Cross41c397a2015-01-12 17:43:04 -0800247
Colin Crossb1abbad2021-04-29 20:16:40 -0700248 for _, r := range removeIdents.idents {
249 m := parser.RemoveStringFromList(list, r)
250 modified = modified || m
251 }
Colin Cross41c397a2015-01-12 17:43:04 -0800252
Colin Crossb1abbad2021-04-29 20:16:40 -0700253 if (wasSorted || *sortLists) && modified {
254 parser.SortList(file, list)
255 }
256 } else if setString != nil {
257 str, ok := value.(*parser.String)
258 if !ok {
259 return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
260 paramName, moduleName, value.Type().String())}
261 }
262
263 str.Value = *setString
264 modified = true
Colin Cross41c397a2015-01-12 17:43:04 -0800265 }
266
267 return modified, nil
268}
269
270func targetedModule(name string) bool {
271 if targetedModules.all {
272 return true
273 }
274 for _, m := range targetedModules.idents {
275 if m == name {
276 return true
277 }
278 }
279
280 return false
281}
282
283func visitFile(path string, f os.FileInfo, err error) error {
284 if err == nil && f.Name() == "Blueprints" {
285 err = processFile(path, nil, os.Stdout)
286 }
287 if err != nil {
288 report(err)
289 }
290 return nil
291}
292
293func walkDir(path string) {
294 filepath.Walk(path, visitFile)
295}
296
297func main() {
Yo Chiang9342b432020-03-05 19:45:49 +0800298 defer func() {
299 if err := recover(); err != nil {
300 report(fmt.Errorf("error: %s", err))
301 }
302 os.Exit(exitCode)
303 }()
Yo Chiange5a91f52020-03-05 11:12:42 +0800304
Colin Cross41c397a2015-01-12 17:43:04 -0800305 flag.Parse()
306
Yo Chiangb138d492020-03-04 20:53:21 +0800307 if len(targetedProperty.parts) == 0 {
308 targetedProperty.Set("deps")
309 }
310
Colin Cross41c397a2015-01-12 17:43:04 -0800311 if flag.NArg() == 0 {
312 if *write {
Yo Chiange5a91f52020-03-05 11:12:42 +0800313 report(fmt.Errorf("error: cannot use -w with standard input"))
Colin Cross41c397a2015-01-12 17:43:04 -0800314 return
315 }
316 if err := processFile("<standard input>", os.Stdin, os.Stdout); err != nil {
317 report(err)
318 }
319 return
320 }
321
322 if len(targetedModules.idents) == 0 {
323 report(fmt.Errorf("-m parameter is required"))
324 return
325 }
326
Colin Crossb1abbad2021-04-29 20:16:40 -0700327 if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil {
328 report(fmt.Errorf("-a, -r or -str parameter is required"))
Colin Cross41c397a2015-01-12 17:43:04 -0800329 return
330 }
331
332 for i := 0; i < flag.NArg(); i++ {
333 path := flag.Arg(i)
334 switch dir, err := os.Stat(path); {
335 case err != nil:
336 report(err)
337 case dir.IsDir():
338 walkDir(path)
339 default:
340 if err := processFile(path, nil, os.Stdout); err != nil {
341 report(err)
342 }
343 }
344 }
345}
346
347func diff(b1, b2 []byte) (data []byte, err error) {
348 f1, err := ioutil.TempFile("", "bpfmt")
349 if err != nil {
350 return
351 }
352 defer os.Remove(f1.Name())
353 defer f1.Close()
354
355 f2, err := ioutil.TempFile("", "bpfmt")
356 if err != nil {
357 return
358 }
359 defer os.Remove(f2.Name())
360 defer f2.Close()
361
362 f1.Write(b1)
363 f2.Write(b2)
364
365 data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
366 if len(data) > 0 {
367 // diff exits with a non-zero status when the files don't match.
368 // Ignore that failure as long as we get output.
369 err = nil
370 }
371 return
372
373}
374
Colin Crossb1abbad2021-04-29 20:16:40 -0700375type stringPtrFlag struct {
376 s **string
377}
378
379func (f stringPtrFlag) Set(s string) error {
380 *f.s = &s
381 return nil
382}
383
384func (f stringPtrFlag) String() string {
385 if f.s == nil || *f.s == nil {
386 return ""
387 }
388 return **f.s
389}
390
Colin Cross41c397a2015-01-12 17:43:04 -0800391type identSet struct {
392 idents []string
393 all bool
394}
395
396func (m *identSet) String() string {
397 return strings.Join(m.idents, ",")
398}
399
400func (m *identSet) Set(s string) error {
401 m.idents = strings.FieldsFunc(s, func(c rune) bool {
402 return unicode.IsSpace(c) || c == ','
403 })
404 if len(m.idents) == 1 && m.idents[0] == "*" {
405 m.all = true
406 }
407 return nil
408}
409
410func (m *identSet) Get() interface{} {
411 return m.idents
412}
Yo Chiangb138d492020-03-04 20:53:21 +0800413
414type qualifiedProperty struct {
415 parts []string
416}
417
418var _ flag.Getter = (*qualifiedProperty)(nil)
419
420func (p *qualifiedProperty) name() string {
421 return p.parts[len(p.parts)-1]
422}
423
424func (p *qualifiedProperty) prefixes() []string {
425 return p.parts[:len(p.parts)-1]
426}
427
428func (p *qualifiedProperty) String() string {
429 return strings.Join(p.parts, ".")
430}
431
432func (p *qualifiedProperty) Set(s string) error {
433 p.parts = strings.Split(s, ".")
434 if len(p.parts) == 0 {
435 return fmt.Errorf("%q is not a valid property name", s)
436 }
437 for _, part := range p.parts {
438 if part == "" {
439 return fmt.Errorf("%q is not a valid property name", s)
440 }
441 }
442 return nil
443}
444
445func (p *qualifiedProperty) Get() interface{} {
446 return p.parts
447}