1
16
17
18
19 package edit
20
21 import (
22 "bufio"
23 "bytes"
24 "errors"
25 "fmt"
26 "io"
27 "io/ioutil"
28 "log"
29 "os"
30 "path/filepath"
31 "regexp"
32 "runtime"
33 "strconv"
34 "strings"
35
36 apipb "github.com/bazelbuild/buildtools/api_proto"
37 "github.com/bazelbuild/buildtools/build"
38 "github.com/bazelbuild/buildtools/edit/bzlmod"
39 "github.com/bazelbuild/buildtools/file"
40 "github.com/bazelbuild/buildtools/labels"
41 "github.com/bazelbuild/buildtools/wspace"
42 "github.com/golang/protobuf/jsonpb"
43 "github.com/golang/protobuf/proto"
44 )
45
46
47 type Options struct {
48 Stdout bool
49 Buildifier string
50 Parallelism int
51 NumIO int
52 CommandsFiles []string
53 KeepGoing bool
54 FilterRuleTypes []string
55 PreferEOLComments bool
56 RootDir string
57 Quiet bool
58 EditVariables bool
59 IsPrintingProto bool
60 IsPrintingJSON bool
61 OutWriter io.Writer
62 ErrWriter io.Writer
63 }
64
65
66 func NewOpts() *Options {
67 return &Options{NumIO: 200, PreferEOLComments: true}
68 }
69
70
71 var Usage = func() {}
72
73 const stdinPackageName = "-"
74
75
76 type CmdEnvironment struct {
77 File *build.File
78 Rule *build.Rule
79 Vars map[string]*build.AssignExpr
80 Pkg string
81 Args []string
82 output *apipb.Output_Record
83 }
84
85
86
87 func cmdAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
88 attr := env.Args[0]
89 for _, val := range env.Args[1:] {
90 if IsIntList(attr) {
91 AddValueToListAttribute(env.Rule, attr, env.Pkg, &build.LiteralExpr{Token: val}, &env.Vars)
92 continue
93 }
94 strVal := getStringExpr(val, env.Pkg)
95 AddValueToListAttribute(env.Rule, attr, env.Pkg, strVal, &env.Vars)
96 }
97 ResolveAttr(env.Rule, attr, env.Pkg)
98 return env.File, nil
99 }
100
101 func cmdComment(opts *Options, env CmdEnvironment) (*build.File, error) {
102
103 str := env.Args[len(env.Args)-1]
104 str = strings.Replace(str, "\\n", "\n", -1)
105
106 fullLine := !opts.PreferEOLComments || strings.Contains(str, "\n")
107 comment := []build.Comment{}
108 for _, line := range strings.Split(str, "\n") {
109 comment = append(comment, build.Comment{Token: "# " + line})
110 }
111
112
113
114 switch len(env.Args) {
115 case 1:
116 env.Rule.Call.Comments.Before = comment
117 case 2:
118 if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
119 if fullLine {
120 attr.LHS.Comment().Before = comment
121 } else {
122 attr.RHS.Comment().Suffix = comment
123 }
124 }
125 case 3:
126 if attr := env.Rule.Attr(env.Args[0]); attr != nil {
127 if expr := listOrSelectFind(attr, env.Args[1], env.Pkg); expr != nil {
128 if fullLine {
129 expr.Comments.Before = comment
130 } else {
131 expr.Comments.Suffix = comment
132 }
133 }
134 }
135 default:
136 panic("cmdComment")
137 }
138 return env.File, nil
139 }
140
141
142 func commentsText(comments []build.Comment) string {
143 var segments []string
144 for _, comment := range comments {
145 token := comment.Token
146 if strings.HasPrefix(token, "#") {
147 token = token[1:]
148 }
149 segments = append(segments, strings.TrimSpace(token))
150 }
151 return strings.Replace(strings.Join(segments, " "), "\n", " ", -1)
152 }
153
154 func cmdPrintComment(opts *Options, env CmdEnvironment) (*build.File, error) {
155 attrError := func() error {
156 return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0])
157 }
158
159 switch len(env.Args) {
160 case 0:
161 env.output.Fields = []*apipb.Output_Record_Field{
162 {Value: &apipb.Output_Record_Field_Text{Text: commentsText(env.Rule.Call.Comments.Before)}},
163 }
164 if text := commentsText(env.Rule.Call.Comments.Suffix); text != "" {
165 env.output.Fields = append(env.output.Fields, &apipb.Output_Record_Field{
166 Value: &apipb.Output_Record_Field_Text{Text: text},
167 })
168 }
169 if text := commentsText(env.Rule.Call.Comments.After); text != "" {
170 env.output.Fields = append(env.output.Fields, &apipb.Output_Record_Field{
171 Value: &apipb.Output_Record_Field_Text{Text: text},
172 })
173 }
174 case 1:
175 attr := env.Rule.AttrDefn(env.Args[0])
176 if attr == nil {
177 env.output.Fields = []*apipb.Output_Record_Field{
178 {Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING}},
179 }
180 return nil, attrError()
181 }
182 comments := append(attr.Before, attr.Suffix...)
183 env.output.Fields = []*apipb.Output_Record_Field{
184 {Value: &apipb.Output_Record_Field_Text{Text: commentsText(comments)}},
185 }
186 case 2:
187 attr := env.Rule.Attr(env.Args[0])
188 if attr == nil {
189 env.output.Fields = []*apipb.Output_Record_Field{
190 {Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING}},
191 }
192 return nil, attrError()
193 }
194 value := env.Args[1]
195 expr := listOrSelectFind(attr, value, env.Pkg)
196 if expr == nil {
197 env.output.Fields = []*apipb.Output_Record_Field{
198 {Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING_LIST_ITEM}},
199 }
200 return nil, fmt.Errorf("attribute \"%s\" has no value \"%s\"", env.Args[0], value)
201 }
202 comments := append(expr.Comments.Before, expr.Comments.Suffix...)
203 env.output.Fields = []*apipb.Output_Record_Field{
204 {Value: &apipb.Output_Record_Field_Text{Text: commentsText(comments)}},
205 }
206 default:
207 panic("cmdPrintComment")
208 }
209 return nil, nil
210 }
211
212 func cmdDelete(opts *Options, env CmdEnvironment) (*build.File, error) {
213 return DeleteRule(env.File, env.Rule), nil
214 }
215
216 func cmdMove(opts *Options, env CmdEnvironment) (*build.File, error) {
217 oldAttr := env.Args[0]
218 newAttr := env.Args[1]
219 if len(env.Args) == 3 && env.Args[2] == "*" {
220 if err := MoveAllListAttributeValues(env.Rule, oldAttr, newAttr, env.Pkg, &env.Vars); err != nil {
221 return nil, err
222 }
223 return env.File, nil
224 }
225 fixed := false
226 for _, val := range env.Args[2:] {
227 if deleted := ListAttributeDelete(env.Rule, oldAttr, val, env.Pkg); deleted != nil {
228 AddValueToListAttribute(env.Rule, newAttr, env.Pkg, deleted, &env.Vars)
229 fixed = true
230 }
231 }
232 if fixed {
233 return env.File, nil
234 }
235 return nil, nil
236 }
237
238 func cmdNew(opts *Options, env CmdEnvironment) (*build.File, error) {
239 kind := env.Args[0]
240 name := env.Args[1]
241 addAtEOF, insertionIndex, err := findInsertionIndex(env)
242 if err != nil {
243 return nil, err
244 }
245
246 if FindRuleByName(env.File, name) != nil {
247 return nil, fmt.Errorf("rule '%s' already exists", name)
248 }
249
250 call := &build.CallExpr{X: &build.Ident{Name: kind}}
251 rule := &build.Rule{Call: call, ImplicitName: ""}
252 rule.SetAttr("name", &build.StringExpr{Value: name})
253
254 if addAtEOF {
255 env.File.Stmt = InsertAfterLastOfSameKind(env.File.Stmt, rule.Call)
256 } else {
257 env.File.Stmt = InsertAfter(insertionIndex, env.File.Stmt, call)
258 }
259 return env.File, nil
260 }
261
262
263 func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
264 if len(env.Args) < 4 {
265 return true, 0, nil
266 }
267
268 relativeToRuleName := env.Args[3]
269 ruleIdx, _ := IndexOfRuleByName(env.File, relativeToRuleName)
270 if ruleIdx == -1 {
271 return true, 0, nil
272 }
273
274 switch env.Args[2] {
275 case "before":
276 return false, ruleIdx - 1, nil
277 case "after":
278 return false, ruleIdx, nil
279 default:
280 return true, 0, fmt.Errorf("Unknown relative operator '%s'; allowed: 'before', 'after'", env.Args[1])
281 }
282 }
283
284
285
286 func splitLoadArgs(args []string) ([]string, []string) {
287 from := args
288 to := append([]string{}, args...)
289 for i := range from {
290 if s := strings.SplitN(from[i], "=", 2); len(s) == 2 {
291 to[i] = s[0]
292 from[i] = s[1]
293 }
294 }
295
296 return from, to
297 }
298
299 func cmdNewLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
300 from, to := splitLoadArgs(env.Args[1:])
301 env.File.Stmt = InsertLoad(env.File.Stmt, env.Args[0], from, to)
302 return env.File, nil
303 }
304
305 func cmdReplaceLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
306 from, to := splitLoadArgs(env.Args[1:])
307 env.File.Stmt = ReplaceLoad(env.File.Stmt, env.Args[0], from, to)
308 return env.File, nil
309 }
310
311 func cmdSubstituteLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
312 oldRegexp, err := regexp.Compile(env.Args[0])
313 if err != nil {
314 return nil, err
315 }
316 newTemplate := env.Args[1]
317
318 for _, stmt := range env.File.Stmt {
319 load, ok := stmt.(*build.LoadStmt)
320 if !ok {
321 continue
322 }
323
324 if newValue, ok := stringSubstitute(load.Module.Value, oldRegexp, newTemplate); ok {
325 load.Module.Value = newValue
326 }
327 }
328
329 return env.File, nil
330 }
331
332 func cmdPrint(opts *Options, env CmdEnvironment) (*build.File, error) {
333 format := env.Args
334 if len(format) == 0 {
335 format = []string{"name", "kind"}
336 }
337 fields := make([]*apipb.Output_Record_Field, len(format))
338
339 for i, str := range format {
340 value := env.Rule.Attr(str)
341 if str == "kind" {
342 fields[i] = &apipb.Output_Record_Field{
343 Value: &apipb.Output_Record_Field_Text{Text: env.Rule.Kind()},
344 }
345 } else if str == "name" {
346 fields[i] = &apipb.Output_Record_Field{
347 Value: &apipb.Output_Record_Field_Text{Text: env.Rule.Name()},
348 }
349 } else if str == "label" {
350 if env.Rule.Name() != "" {
351 label := labels.Label{Package: env.Pkg, Target: env.Rule.Name()}
352 fields[i] = &apipb.Output_Record_Field{
353 Value: &apipb.Output_Record_Field_Text{Text: label.Format()},
354 }
355 } else {
356 return nil, nil
357 }
358 } else if str == "rule" {
359 fields[i] = &apipb.Output_Record_Field{
360 Value: &apipb.Output_Record_Field_Text{Text: build.FormatString(env.Rule.Call)},
361 }
362 } else if str == "startline" {
363 fields[i] = &apipb.Output_Record_Field{
364 Value: &apipb.Output_Record_Field_Number{Number: int32(env.Rule.Call.ListStart.Line)},
365 }
366 } else if str == "endline" {
367 fields[i] = &apipb.Output_Record_Field{
368 Value: &apipb.Output_Record_Field_Number{Number: int32(env.Rule.Call.End.Pos.Line)},
369 }
370 } else if str == "path" {
371 fields[i] = &apipb.Output_Record_Field{
372 Value: &apipb.Output_Record_Field_Text{Text: env.File.Path},
373 }
374 } else if value == nil {
375 fmt.Fprintf(opts.ErrWriter, "rule \"//%s:%s\" has no attribute \"%s\"\n",
376 env.Pkg, env.Rule.Name(), str)
377 fields[i] = &apipb.Output_Record_Field{
378 Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING},
379 }
380 } else if lit, ok := value.(*build.LiteralExpr); ok {
381 fields[i] = &apipb.Output_Record_Field{
382 Value: &apipb.Output_Record_Field_Text{Text: lit.Token},
383 }
384 } else if lit, ok := value.(*build.Ident); ok {
385 fields[i] = &apipb.Output_Record_Field{
386 Value: &apipb.Output_Record_Field_Text{Text: lit.Name},
387 }
388 } else if string, ok := value.(*build.StringExpr); ok {
389 fields[i] = &apipb.Output_Record_Field{
390 Value: &apipb.Output_Record_Field_Text{Text: string.Value},
391 QuoteWhenPrinting: true,
392 }
393 } else if strList := env.Rule.AttrStrings(str); strList != nil {
394 fields[i] = &apipb.Output_Record_Field{
395 Value: &apipb.Output_Record_Field_List{List: &apipb.RepeatedString{Strings: strList}},
396 }
397 } else {
398
399 fields[i] = &apipb.Output_Record_Field{
400 Value: &apipb.Output_Record_Field_Text{Text: build.FormatString(value)},
401 }
402 }
403 }
404
405 env.output.Fields = fields
406 return nil, nil
407 }
408
409 func attrKeysForPattern(rule *build.Rule, pattern string) []string {
410 if pattern == "*" {
411 return rule.AttrKeys()
412 }
413 return []string{pattern}
414 }
415
416 func cmdRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
417 if len(env.Args) == 1 {
418 if env.Args[0] == "*" {
419 didDelete := false
420 for _, attr := range env.Rule.AttrKeys() {
421 if attr == "name" {
422 continue
423 }
424 if env.Rule.DelAttr(attr) != nil {
425 didDelete = true
426 }
427 }
428 if didDelete {
429 return env.File, nil
430 }
431 } else {
432 if env.Rule.DelAttr(env.Args[0]) != nil {
433 return env.File, nil
434 }
435 }
436 } else {
437 fixed := false
438 for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
439 for _, val := range env.Args[1:] {
440 ListAttributeDelete(env.Rule, key, val, env.Pkg)
441 fixed = true
442 }
443 ResolveAttr(env.Rule, key, env.Pkg)
444
445 if listExpr, ok := env.Rule.Attr(key).(*build.ListExpr); ok && len(listExpr.List) == 0 {
446 env.Rule.DelAttr(key)
447 }
448 }
449 if fixed {
450 return env.File, nil
451 }
452 }
453 return nil, nil
454 }
455
456 func cmdRemoveIfEqual(opts *Options, env CmdEnvironment) (*build.File, error) {
457 attr := env.Args[0]
458 val := env.Args[1]
459
460 var equal bool
461 switch input := env.Rule.Attr(attr).(type) {
462 case *build.StringExpr:
463 equal = labels.Equal(input.Value, val, env.Pkg)
464 case *build.Ident:
465 equal = input.Name == val
466 default:
467 return nil, nil
468 }
469
470 if !equal {
471 return nil, nil
472 }
473
474 env.Rule.DelAttr(attr)
475 return env.File, nil
476 }
477
478 func cmdRemoveComment(opts *Options, env CmdEnvironment) (*build.File, error) {
479 switch len(env.Args) {
480 case 0:
481 env.Rule.Call.Comments.Before = nil
482 env.Rule.Call.Comments.Suffix = nil
483 env.Rule.Call.Comments.After = nil
484 case 1:
485 if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
486 attr.Comments.Before = nil
487 attr.Comments.Suffix = nil
488 attr.Comments.After = nil
489 attr.LHS.Comment().Before = nil
490 attr.LHS.Comment().Suffix = nil
491 attr.LHS.Comment().After = nil
492 attr.RHS.Comment().Before = nil
493 attr.RHS.Comment().Suffix = nil
494 attr.RHS.Comment().After = nil
495 }
496 case 2:
497 if attr := env.Rule.Attr(env.Args[0]); attr != nil {
498 if expr := listOrSelectFind(attr, env.Args[1], env.Pkg); expr != nil {
499 expr.Comments.Before = nil
500 expr.Comments.Suffix = nil
501 expr.Comments.After = nil
502 }
503 }
504 default:
505 panic("cmdRemoveComment")
506 }
507 return env.File, nil
508 }
509
510 func cmdRename(opts *Options, env CmdEnvironment) (*build.File, error) {
511 oldAttr := env.Args[0]
512 newAttr := env.Args[1]
513 if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil {
514 return nil, err
515 }
516 return env.File, nil
517 }
518
519 func cmdReplace(opts *Options, env CmdEnvironment) (*build.File, error) {
520 oldV := getStringValue(env.Args[1])
521 newV := getStringValue(env.Args[2])
522 for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
523 attr := env.Rule.Attr(key)
524 if e, ok := attr.(*build.StringExpr); ok {
525 if labels.Equal(e.Value, oldV, env.Pkg) {
526 env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newV}, env))
527 }
528 } else {
529 ListReplace(attr, oldV, newV, env.Pkg)
530 }
531 }
532 return env.File, nil
533 }
534
535 func cmdSubstitute(opts *Options, env CmdEnvironment) (*build.File, error) {
536 oldRegexp, err := regexp.Compile(env.Args[1])
537 if err != nil {
538 return nil, err
539 }
540 newTemplate := env.Args[2]
541 for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
542 attr := env.Rule.Attr(key)
543 e, ok := attr.(*build.StringExpr)
544 if !ok {
545 ListSubstitute(attr, oldRegexp, newTemplate)
546 continue
547 }
548 if newValue, ok := stringSubstitute(e.Value, oldRegexp, newTemplate); ok {
549 env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newValue}, env))
550 }
551 }
552 return env.File, nil
553 }
554
555 func cmdSet(opts *Options, env CmdEnvironment) (*build.File, error) {
556 attr := env.Args[0]
557 args := env.Args[1:]
558 if attr == "kind" {
559 env.Rule.SetKind(args[0])
560 } else {
561 env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
562 }
563 return env.File, nil
564 }
565
566 func cmdSetIfAbsent(opts *Options, env CmdEnvironment) (*build.File, error) {
567 attr := env.Args[0]
568 args := env.Args[1:]
569 if attr == "kind" {
570 return nil, fmt.Errorf("setting 'kind' is not allowed for set_if_absent. Got %s", env.Args)
571 }
572 if env.Rule.Attr(attr) == nil {
573 env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
574 }
575 return env.File, nil
576 }
577
578 func getAttrValueExpr(attr string, args []string, env CmdEnvironment) build.Expr {
579 switch {
580 case attr == "kind":
581 return nil
582 case IsIntList(attr):
583 var list []build.Expr
584 for _, i := range args {
585 list = append(list, &build.LiteralExpr{Token: i})
586 }
587 return &build.ListExpr{List: list}
588 case IsList(attr) && !(len(args) == 1 && strings.HasPrefix(args[0], "glob(")):
589 var list []build.Expr
590 for _, arg := range args {
591 list = append(list, getStringExpr(arg, env.Pkg))
592 }
593 return &build.ListExpr{List: list}
594 case len(args) == 0:
595
596 return &build.Ident{Name: "None"}
597 case IsString(attr):
598 return getStringExpr(args[0], env.Pkg)
599 default:
600 return &build.Ident{Name: args[0]}
601 }
602 }
603
604
605 func getStringValue(value string) string {
606 if unquoted, _, err := build.Unquote(value); err == nil {
607 return unquoted
608 }
609 return value
610 }
611
612
613
614 func getStringExpr(value, pkg string) build.Expr {
615 if unquoted, triple, err := build.Unquote(value); err == nil {
616 return &build.StringExpr{Value: ShortenLabel(unquoted, pkg), TripleQuote: triple}
617 }
618 return &build.StringExpr{Value: ShortenLabel(value, pkg)}
619 }
620
621 func cmdCopy(opts *Options, env CmdEnvironment) (*build.File, error) {
622 attrName := env.Args[0]
623 from := env.Args[1]
624
625 return copyAttributeBetweenRules(env, attrName, from)
626 }
627
628 func cmdCopyNoOverwrite(opts *Options, env CmdEnvironment) (*build.File, error) {
629 attrName := env.Args[0]
630 from := env.Args[1]
631
632 if env.Rule.Attr(attrName) != nil {
633 return env.File, nil
634 }
635
636 return copyAttributeBetweenRules(env, attrName, from)
637 }
638
639
640 func cmdDictAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
641 attr := env.Args[0]
642 args := env.Args[1:]
643
644 dict := &build.DictExpr{}
645 currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
646 if ok {
647 dict = currDict
648 }
649
650 for _, x := range args {
651 kv := strings.SplitN(x, ":", 2)
652 if len(kv) != 2 {
653 return nil, fmt.Errorf("no colon in dict_add argument %q found", x)
654 }
655 expr := getStringExpr(kv[1], env.Pkg)
656
657 prev := DictionaryGet(dict, kv[0])
658 if prev == nil {
659
660 DictionarySet(dict, kv[0], expr)
661 }
662 }
663 env.Rule.SetAttr(attr, dict)
664 return env.File, nil
665 }
666
667 func cmdSetSelect(opts *Options, env CmdEnvironment) (*build.File, error) {
668 attr := env.Args[0]
669 args := env.Args[1:]
670
671 dict := &build.DictExpr{}
672
673 if len(args)%2 != 0 {
674 return nil, fmt.Errorf("no value passed for last key: %s", args[len(args)-1])
675 }
676 for i := 0; i < len(args); i += 2 {
677 key := args[i]
678 value := args[i+1]
679 var expr build.Expr
680 if IsList(attr) {
681 list := &build.ListExpr{}
682 if cur := DictionaryGet(dict, key); cur != nil {
683 list = cur.(*build.ListExpr)
684 }
685 AddValueToList(list, env.Pkg, getStringExpr(value, env.Pkg), !attributeMustNotBeSorted(env.Rule.Name(), attr))
686 expr = list
687 } else {
688 expr = getStringExpr(value, env.Pkg)
689 }
690
691 DictionarySet(dict, key, expr)
692 }
693 call := &build.CallExpr{List: []build.Expr{dict}}
694 call.X = &build.Ident{Name: "select"}
695 env.Rule.SetAttr(attr, call)
696 return env.File, nil
697 }
698
699
700 func cmdDictSet(opts *Options, env CmdEnvironment) (*build.File, error) {
701 attr := env.Args[0]
702 args := env.Args[1:]
703
704 dict := &build.DictExpr{}
705 currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
706 if ok {
707 dict = currDict
708 }
709
710 for _, x := range args {
711 kv := strings.SplitN(x, ":", 2)
712 if len(kv) != 2 {
713 return nil, fmt.Errorf("no colon in dict_set argument %q found", x)
714 }
715 expr := getStringExpr(kv[1], env.Pkg)
716
717 DictionarySet(dict, kv[0], expr)
718 }
719 env.Rule.SetAttr(attr, dict)
720 return env.File, nil
721 }
722
723
724 func cmdDictRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
725 attr := env.Args[0]
726 args := env.Args[1:]
727
728 thing := env.Rule.Attr(attr)
729 dictAttr, ok := thing.(*build.DictExpr)
730 if !ok {
731 return env.File, nil
732 }
733
734 for _, x := range args {
735
736 DictionaryDelete(dictAttr, x)
737 env.Rule.SetAttr(attr, dictAttr)
738 }
739
740
741 if dictAttr == nil || len(dictAttr.List) == 0 {
742 env.Rule.DelAttr(attr)
743 }
744
745 return env.File, nil
746 }
747
748
749 func cmdDictListAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
750 attr := env.Args[0]
751 key := env.Args[1]
752 args := env.Args[2:]
753
754 dict := &build.DictExpr{}
755 if currDict, ok := env.Rule.Attr(attr).(*build.DictExpr); ok {
756 dict = currDict
757 }
758
759 prev := DictionaryGet(dict, key)
760 if prev == nil {
761 prev = &build.ListExpr{}
762 }
763
764 for _, val := range args {
765 expr := getStringExpr(val, env.Pkg)
766 prev = AddValueToList(prev, env.Pkg, expr, true)
767 }
768
769 DictionarySet(dict, key, prev)
770 env.Rule.SetAttr(attr, dict)
771
772 return env.File, nil
773 }
774
775 func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string) (*build.File, error) {
776 fromRule := FindRuleByName(env.File, from)
777 if fromRule == nil {
778 return nil, fmt.Errorf("could not find rule '%s'", from)
779 }
780 attr := fromRule.Attr(attrName)
781 if attr == nil {
782 return nil, fmt.Errorf("rule '%s' does not have attribute '%s'", from, attrName)
783 }
784
785 ast, err := build.ParseBuild("" , []byte(build.FormatString(attr)))
786 if err != nil {
787 return nil, fmt.Errorf("could not parse attribute value %v", build.FormatString(attr))
788 }
789
790 env.Rule.SetAttr(attrName, ast.Stmt[0])
791 return env.File, nil
792 }
793
794 func cmdUseRepoAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
795 return cmdImplUseRepo(env, "use_repo_add")
796 }
797
798 func cmdUseRepoRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
799 return cmdImplUseRepo(env, "use_repo_remove")
800 }
801
802 func cmdImplUseRepo(env CmdEnvironment, mode string) (*build.File, error) {
803 if env.File.Type != build.TypeModule {
804 return nil, fmt.Errorf("%s: only applies to MODULE.bazel files", mode)
805 }
806
807 dev := false
808 args := env.Args
809 if env.Args[0] == "dev" && isExtensionLabel(env.Args[1]) {
810 dev = true
811 args = env.Args[1:]
812 }
813
814 var proxies []string
815 var repos []string
816 if isExtensionLabel(args[0]) {
817 extBzlFile := args[0]
818 extName := args[1]
819
820 proxies = bzlmod.Proxies(env.File, extBzlFile, extName, dev)
821 if len(proxies) == 0 {
822 return nil, fmt.Errorf("%s: no use_extension assignment found for extension %q defined in %q", mode, extName, extBzlFile)
823 }
824 repos = args[2:]
825 } else {
826 proxy := args[0]
827
828 proxies = bzlmod.AllProxies(env.File, proxy)
829 if len(proxies) == 0 {
830 return nil, fmt.Errorf("%s: no use_extension assignment to variable %q found", mode, proxy)
831 }
832 repos = args[1:]
833 }
834
835 useRepos := bzlmod.UseRepos(env.File, proxies)
836 if len(useRepos) == 0 {
837 var newUseRepo *build.CallExpr
838 env.File, newUseRepo = bzlmod.NewUseRepo(env.File, proxies)
839 useRepos = []*build.CallExpr{newUseRepo}
840 }
841
842 if mode == "use_repo_add" {
843 bzlmod.AddRepoUsages(useRepos, repos...)
844 } else {
845 bzlmod.RemoveRepoUsages(useRepos, repos...)
846 }
847
848 return env.File, nil
849 }
850
851 func cmdFormat(opts *Options, env CmdEnvironment) (*build.File, error) {
852
853 return env.File, nil
854 }
855
856 func isExtensionLabel(arg string) bool {
857
858
859 return strings.HasPrefix(arg, "@") || strings.HasSuffix(arg, "//")
860 }
861
862 func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
863
864 if env.Rule.Kind() == "package" {
865 return FixFile(env.File, env.Pkg, env.Args), nil
866 }
867
868 return FixRule(env.File, env.Pkg, env.Rule, env.Args), nil
869 }
870
871
872 type CommandInfo struct {
873 Fn func(*Options, CmdEnvironment) (*build.File, error)
874 PerRule bool
875 MinArg int
876 MaxArg int
877 Template string
878 }
879
880
881
882 var AllCommands = map[string]CommandInfo{
883 "add": {cmdAdd, true, 2, -1, "<attr> <value(s)>"},
884 "new_load": {cmdNewLoad, false, 1, -1, "<path> <[to=]from(s)>"},
885 "replace_load": {cmdReplaceLoad, false, 1, -1, "<path> <[to=]symbol(s)>"},
886 "substitute_load": {cmdSubstituteLoad, false, 2, 2, "<old_regexp> <new_template>"},
887 "comment": {cmdComment, true, 1, 3, "<attr>? <value>? <comment>"},
888 "print_comment": {cmdPrintComment, true, 0, 2, "<attr>? <value>?"},
889 "delete": {cmdDelete, true, 0, 0, ""},
890 "fix": {cmdFix, true, 0, -1, "<fix(es)>?"},
891 "move": {cmdMove, true, 3, -1, "<old_attr> <new_attr> <value(s)>"},
892 "new": {cmdNew, false, 2, 4, "<rule_kind> <rule_name> [(before|after) <relative_rule_name>]"},
893 "print": {cmdPrint, true, 0, -1, "<attribute(s)>"},
894 "remove": {cmdRemove, true, 1, -1, "<attr> <value(s)>"},
895 "remove_comment": {cmdRemoveComment, true, 0, 2, "<attr>? <value>?"},
896 "remove_if_equal": {cmdRemoveIfEqual, true, 2, 2, "<attr> <value>"},
897 "rename": {cmdRename, true, 2, 2, "<old_attr> <new_attr>"},
898 "replace": {cmdReplace, true, 3, 3, "<attr> <old_value> <new_value>"},
899 "substitute": {cmdSubstitute, true, 3, 3, "<attr> <old_regexp> <new_template>"},
900 "set": {cmdSet, true, 1, -1, "<attr> <value(s)>"},
901 "set_if_absent": {cmdSetIfAbsent, true, 1, -1, "<attr> <value(s)>"},
902 "set_select": {cmdSetSelect, true, 1, -1, "<attr> <key_1> <value_1> <key_n> <value_n>"},
903 "copy": {cmdCopy, true, 2, 2, "<attr> <from_rule>"},
904 "copy_no_overwrite": {cmdCopyNoOverwrite, true, 2, 2, "<attr> <from_rule>"},
905 "dict_add": {cmdDictAdd, true, 2, -1, "<attr> <(key:value)(s)>"},
906 "dict_set": {cmdDictSet, true, 2, -1, "<attr> <(key:value)(s)>"},
907 "dict_remove": {cmdDictRemove, true, 2, -1, "<attr> <key(s)>"},
908 "dict_list_add": {cmdDictListAdd, true, 3, -1, "<attr> <key> <value(s)>"},
909 "use_repo_add": {cmdUseRepoAdd, false, 2, -1, "([dev] <extension .bzl file> <extension name>|<use_extension variable name>) <repo(s)>"},
910 "use_repo_remove": {cmdUseRepoRemove, false, 2, -1, "([dev] <extension .bzl file> <extension name>|<use_extension variable name>) <repo(s)>"},
911 "format": {cmdFormat, false, 0, 0, ""},
912 }
913
914 var readonlyCommands = map[string]bool{
915 "print": true,
916 "print_comment": true,
917 }
918
919 func expandTargets(f *build.File, rule string) ([]*build.Rule, error) {
920 if r := FindRuleByName(f, rule); r != nil {
921 return []*build.Rule{r}, nil
922 } else if r := FindExportedFile(f, rule); r != nil {
923 return []*build.Rule{r}, nil
924 } else if rule == "all" || rule == "*" {
925
926 return f.Rules(""), nil
927 } else if strings.HasPrefix(rule, "%") {
928
929
930
931 kind := rule[1:]
932 if linenum, err := strconv.Atoi(kind); err == nil {
933 if r := f.RuleAt(linenum); r != nil {
934 return []*build.Rule{r}, nil
935 }
936 } else {
937 return f.Rules(kind), nil
938 }
939 }
940 return nil, fmt.Errorf("rule '%s' not found", rule)
941 }
942
943 func filterRules(opts *Options, rules []*build.Rule) (result []*build.Rule) {
944 if len(opts.FilterRuleTypes) == 0 {
945 return rules
946 }
947 for _, rule := range rules {
948 for _, filterType := range opts.FilterRuleTypes {
949 if rule.Kind() == filterType {
950 result = append(result, rule)
951 break
952 }
953 }
954 }
955 return
956 }
957
958
959 type command struct {
960 tokens []string
961 }
962
963
964
965 func checkCommandUsage(opts *Options, name string, cmd CommandInfo, count int) {
966 if count >= cmd.MinArg && (cmd.MaxArg == -1 || count <= cmd.MaxArg) {
967 return
968 }
969
970 if count < cmd.MinArg {
971 fmt.Fprintf(opts.ErrWriter, "Too few arguments for command '%s', expected at least %d.\n",
972 name, cmd.MinArg)
973 } else {
974 fmt.Fprintf(opts.ErrWriter, "Too many arguments for command '%s', expected at most %d.\n",
975 name, cmd.MaxArg)
976 }
977 Usage()
978 os.Exit(1)
979 }
980
981
982 var spaceRegex = regexp.MustCompile(`(\\ |\\\n|[^ \n])+`)
983
984
985
986
987 func SplitOnSpaces(input string) []string {
988 result := spaceRegex.FindAllString(input, -1)
989 for i, s := range result {
990 s = strings.Replace(s, `\ `, " ", -1)
991 s = strings.Replace(s, "\\\n", "\n", -1)
992 result[i] = s
993 }
994 return result
995 }
996
997
998
999
1000
1001
1002
1003
1004 func parseCommands(opts *Options, args []string) (commands []command, targets []string, err error) {
1005 for _, arg := range args {
1006 commandTokens := SplitOnSpaces(arg)
1007 if len(commandTokens) == 0 {
1008 return nil, nil, fmt.Errorf("empty command list")
1009 }
1010
1011 cmd, found := AllCommands[commandTokens[0]]
1012 if found {
1013 checkCommandUsage(opts, commandTokens[0], cmd, len(commandTokens)-1)
1014 commands = append(commands, command{commandTokens})
1015 } else {
1016 targets = append(targets, arg)
1017 }
1018 }
1019 return
1020 }
1021
1022
1023 type commandsForTarget struct {
1024 target string
1025 commands []command
1026 }
1027
1028
1029
1030 type commandsForFile struct {
1031 file string
1032 commands []commandsForTarget
1033 }
1034
1035
1036
1037 func commandError(commands []command, target string, err error) error {
1038 return fmt.Errorf("error while executing commands %s on target %s: %s", commands, target, err)
1039 }
1040
1041
1042 type rewriteResult struct {
1043 file string
1044 errs []error
1045 modified bool
1046 records []*apipb.Output_Record
1047 }
1048
1049
1050
1051
1052
1053
1054
1055 func getGlobalVariables(exprs []build.Expr) (vars map[string]*build.AssignExpr) {
1056 vars = make(map[string]*build.AssignExpr)
1057 for _, expr := range exprs {
1058 if as, ok := expr.(*build.AssignExpr); ok {
1059 if lhs, ok := as.LHS.(*build.Ident); ok {
1060 vars[lhs.Name] = as
1061 }
1062 }
1063 }
1064 return vars
1065 }
1066
1067
1068
1069
1070
1071
1072
1073
1074 var BuildFileNames = [...]string{"BUILD.bazel", "BUILD", "BUCK"}
1075
1076
1077 type Buildifier interface {
1078
1079 Buildify(*Options, *build.File) ([]byte, error)
1080 }
1081
1082 var (
1083 buildifier Buildifier = &defaultBuildifier{}
1084 buildifierRegistered = false
1085 )
1086
1087
1088
1089
1090
1091 func RegisterBuildifier(b Buildifier) {
1092 if buildifierRegistered {
1093 panic("Only one call to RegisterBuildifier is allowed.")
1094 }
1095 buildifier = b
1096 buildifierRegistered = true
1097 }
1098
1099
1100
1101 func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
1102 name := commandsForFile.file
1103 var data []byte
1104 var err error
1105 var fi os.FileInfo
1106 records := []*apipb.Output_Record{}
1107 if name == stdinPackageName {
1108 data, err = ioutil.ReadAll(os.Stdin)
1109 if err != nil {
1110 return &rewriteResult{file: name, errs: []error{err}}
1111 }
1112 } else {
1113 origName := name
1114 for _, suffix := range BuildFileNames {
1115 if strings.HasSuffix(name, "/"+suffix) {
1116 name = strings.TrimSuffix(name, suffix)
1117 break
1118 }
1119 }
1120 for _, suffix := range BuildFileNames {
1121 name = name + suffix
1122 data, fi, err = file.ReadFile(name)
1123 if err == nil {
1124 break
1125 }
1126 name = strings.TrimSuffix(name, suffix)
1127 }
1128 if err != nil {
1129 data, fi, err = file.ReadFile(name)
1130 }
1131 if err != nil {
1132 err = errors.New("file not found or not readable")
1133 return &rewriteResult{file: origName, errs: []error{err}}
1134 }
1135 }
1136
1137 f, err := build.Parse(name, data)
1138 if err != nil {
1139 return &rewriteResult{file: name, errs: []error{err}}
1140 }
1141 if f.Type == build.TypeDefault {
1142
1143 f.Type = build.TypeBuild
1144 }
1145 f.WorkspaceRoot, f.Pkg, f.Label = wspace.SplitFilePath(name)
1146
1147 vars := map[string]*build.AssignExpr{}
1148 if opts.EditVariables {
1149 vars = getGlobalVariables(f.Stmt)
1150 }
1151 var errs []error
1152 changed := false
1153 for _, commands := range commandsForFile.commands {
1154 target := commands.target
1155 commands := commands.commands
1156 _, _, absPkg, rule := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
1157 if label := labels.Parse(target); label.Package == stdinPackageName {
1158
1159 absPkg = stdinPackageName
1160 }
1161 if strings.HasSuffix(absPkg, "...") {
1162
1163 absPkg = f.Pkg
1164 }
1165
1166 targets, err := expandTargets(f, rule)
1167 if err != nil {
1168 cerr := commandError(commands, target, err)
1169 errs = append(errs, cerr)
1170 if !opts.KeepGoing {
1171 return &rewriteResult{file: name, errs: errs, records: records}
1172 }
1173 }
1174 targets = filterRules(opts, targets)
1175 for _, cmd := range commands {
1176 cmdInfo := AllCommands[cmd.tokens[0]]
1177
1178
1179 cmdTargets := targets
1180 if !cmdInfo.PerRule {
1181 cmdTargets = []*build.Rule{nil}
1182 }
1183 for _, r := range cmdTargets {
1184 record := &apipb.Output_Record{}
1185 newf, err := cmdInfo.Fn(opts, CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
1186 if len(record.Fields) != 0 {
1187 records = append(records, record)
1188 }
1189 if err != nil {
1190 cerr := commandError([]command{cmd}, target, err)
1191 if opts.KeepGoing {
1192 errs = append(errs, cerr)
1193 } else {
1194 return &rewriteResult{file: name, errs: []error{cerr}, records: records}
1195 }
1196 }
1197 if newf != nil {
1198 changed = true
1199 f = newf
1200 }
1201 }
1202 }
1203 }
1204 if !changed {
1205 return &rewriteResult{file: name, errs: errs, records: records}
1206 }
1207 f = RemoveEmptyPackage(f)
1208 f = RemoveEmptyUseRepoCalls(f)
1209 ndata, err := buildifier.Buildify(opts, f)
1210 if err != nil {
1211 return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
1212 }
1213
1214 if opts.Stdout || name == stdinPackageName {
1215 opts.OutWriter.Write(ndata)
1216 return &rewriteResult{file: name, errs: errs, records: records}
1217 }
1218
1219 if bytes.Equal(data, ndata) {
1220 return &rewriteResult{file: name, errs: errs, records: records}
1221 }
1222
1223 if err := EditFile(fi, name); err != nil {
1224 return &rewriteResult{file: name, errs: []error{err}, records: records}
1225 }
1226
1227 if err := file.WriteFile(name, ndata); err != nil {
1228 return &rewriteResult{file: name, errs: []error{err}, records: records}
1229 }
1230
1231 return &rewriteResult{file: name, errs: errs, modified: true, records: records}
1232 }
1233
1234
1235
1236 var EditFile = func(fi os.FileInfo, name string) error {
1237 return nil
1238 }
1239
1240
1241
1242 func targetExpressionToBuildFiles(rootDir string, target string) []string {
1243 file, _, _, _ := InterpretLabelForWorkspaceLocation(rootDir, target)
1244 if rootDir == "" {
1245 var err error
1246 if file, err = filepath.Abs(file); err != nil {
1247 fmt.Printf("Cannot make path absolute: %s\n", err.Error())
1248 os.Exit(1)
1249 }
1250 }
1251
1252 suffix := filepath.Join("", "...", "BUILD")
1253 if !strings.HasSuffix(file, suffix) {
1254 return []string{file}
1255 }
1256
1257 return findBuildFiles(strings.TrimSuffix(file, suffix))
1258 }
1259
1260
1261 func findBuildFiles(rootDir string) []string {
1262 var buildFiles []string
1263 searchDirs := []string{rootDir}
1264
1265 for len(searchDirs) != 0 {
1266 lastIndex := len(searchDirs) - 1
1267 dir := searchDirs[lastIndex]
1268 searchDirs = searchDirs[:lastIndex]
1269
1270 dirFiles, err := ioutil.ReadDir(dir)
1271 if err != nil {
1272 continue
1273 }
1274
1275 for _, dirFile := range dirFiles {
1276 if dirFile.IsDir() {
1277 searchDirs = append(searchDirs, filepath.Join(dir, dirFile.Name()))
1278 } else {
1279 for _, buildFileName := range BuildFileNames {
1280 if dirFile.Name() == buildFileName {
1281 buildFiles = append(buildFiles, filepath.Join(dir, dirFile.Name()))
1282 }
1283 }
1284 }
1285 }
1286 }
1287
1288 return buildFiles
1289 }
1290
1291
1292
1293 func appendCommands(opts *Options, commandMap map[string][]commandsForTarget, args []string) error {
1294 commands, targets, err := parseCommands(opts, args)
1295 if err != nil {
1296 return err
1297 }
1298 for _, target := range targets {
1299 for _, buildFileName := range BuildFileNames {
1300 if strings.HasSuffix(target, filepath.FromSlash("/"+buildFileName)) {
1301 target = strings.TrimSuffix(target, filepath.FromSlash("/"+buildFileName)) + ":__pkg__"
1302 } else if strings.HasSuffix(target, "/"+buildFileName) {
1303 target = strings.TrimSuffix(target, "/"+buildFileName) + ":__pkg__"
1304 }
1305 }
1306 var buildFiles []string
1307 if label := labels.Parse(target); label.Package == stdinPackageName {
1308 buildFiles = []string{stdinPackageName}
1309 } else {
1310 buildFiles = targetExpressionToBuildFiles(opts.RootDir, target)
1311 }
1312
1313 for _, file := range buildFiles {
1314 commandMap[file] = append(commandMap[file], commandsForTarget{target, commands})
1315 }
1316 }
1317 return nil
1318 }
1319
1320 func appendCommandsFromFiles(opts *Options, commandsByFile map[string][]commandsForTarget, labels []string) error {
1321 for _, fileName := range opts.CommandsFiles {
1322 var reader io.Reader
1323 if fileName == stdinPackageName {
1324 reader = os.Stdin
1325 } else {
1326 rc := file.OpenReadFile(fileName)
1327 reader = rc
1328 defer rc.Close()
1329 }
1330 if err := appendCommandsFromReader(opts, reader, commandsByFile, labels); err != nil {
1331 return err
1332 }
1333 }
1334 return nil
1335 }
1336
1337 func appendCommandsFromReader(opts *Options, reader io.Reader, commandsByFile map[string][]commandsForTarget, labels []string) error {
1338 r := bufio.NewReader(reader)
1339 atEOF := false
1340 for !atEOF {
1341 line, err := r.ReadString('\n')
1342 if err == io.EOF {
1343 atEOF = true
1344 err = nil
1345 }
1346 if err != nil {
1347 return fmt.Errorf("error while reading commands file: %v", err)
1348 }
1349 line = strings.TrimSpace(line)
1350 if line == "" || line[0] == '#' {
1351 continue
1352 }
1353 line = saveEscapedPipes(line)
1354 args := strings.Split(line, "|")
1355 for i, arg := range args {
1356 args[i] = replaceSavedPipes(arg)
1357 }
1358 if len(args) > 1 && args[1] == "*" {
1359 cmd := append([]string{args[0]}, labels...)
1360 if err := appendCommands(opts, commandsByFile, cmd); err != nil {
1361 return err
1362 }
1363 } else {
1364 if err := appendCommands(opts, commandsByFile, args); err != nil {
1365 return err
1366 }
1367 }
1368 }
1369 return nil
1370 }
1371
1372 func saveEscapedPipes(s string) string {
1373 return strings.ReplaceAll(s, `\|`, "\x00\x00")
1374 }
1375
1376 func replaceSavedPipes(s string) string {
1377 return strings.ReplaceAll(s, "\x00\x00", "|")
1378 }
1379
1380 func printRecord(writer io.Writer, record *apipb.Output_Record) {
1381 fields := record.Fields
1382 line := make([]string, len(fields))
1383 for i, field := range fields {
1384 switch value := field.Value.(type) {
1385 case *apipb.Output_Record_Field_Text:
1386 if field.QuoteWhenPrinting && strings.ContainsRune(value.Text, ' ') {
1387 line[i] = fmt.Sprintf("%q", value.Text)
1388 } else {
1389 line[i] = value.Text
1390 }
1391 case *apipb.Output_Record_Field_Number:
1392 line[i] = strconv.Itoa(int(value.Number))
1393 case *apipb.Output_Record_Field_Error:
1394 switch value.Error {
1395 case apipb.Output_Record_Field_UNKNOWN:
1396 line[i] = "(unknown)"
1397 case apipb.Output_Record_Field_MISSING:
1398 line[i] = "(missing)"
1399 }
1400 case *apipb.Output_Record_Field_List:
1401 line[i] = fmt.Sprintf("[%s]", strings.Join(value.List.Strings, " "))
1402 }
1403 }
1404
1405 fmt.Fprint(writer, strings.Join(line, " ")+"\n")
1406 }
1407
1408
1409 func Buildozer(opts *Options, args []string) int {
1410 if opts.OutWriter == nil {
1411 opts.OutWriter = os.Stdout
1412 }
1413 if opts.ErrWriter == nil {
1414 opts.ErrWriter = os.Stderr
1415 }
1416 commandsByFile := make(map[string][]commandsForTarget)
1417 if len(opts.CommandsFiles) > 0 {
1418 if err := appendCommandsFromFiles(opts, commandsByFile, args); err != nil {
1419 fmt.Fprintf(opts.ErrWriter, "error: %s\n", err)
1420 return 1
1421 }
1422 } else {
1423 if len(args) == 0 {
1424 Usage()
1425 }
1426 if err := appendCommands(opts, commandsByFile, args); err != nil {
1427 fmt.Fprintf(opts.ErrWriter, "error: %s\n", err)
1428 return 1
1429 }
1430 }
1431
1432 numFiles := len(commandsByFile)
1433 if opts.Parallelism > 0 {
1434 runtime.GOMAXPROCS(opts.Parallelism)
1435 }
1436 results := make(chan *rewriteResult, numFiles)
1437 data := make(chan commandsForFile)
1438
1439 if opts.NumIO < 1 {
1440 fmt.Fprintf(opts.ErrWriter, "NumIO must be at least 1; got %d (are you using `NewOpts`?)\n", opts.NumIO)
1441 return 1
1442 }
1443 for i := 0; i < opts.NumIO; i++ {
1444 go func(results chan *rewriteResult, data chan commandsForFile) {
1445 for commandsForFile := range data {
1446 results <- rewrite(opts, commandsForFile)
1447 }
1448 }(results, data)
1449 }
1450
1451 for file, commands := range commandsByFile {
1452 data <- commandsForFile{file, commands}
1453 }
1454 close(data)
1455 records := []*apipb.Output_Record{}
1456 var hasErrors bool
1457 var fileModified bool
1458 for i := 0; i < numFiles; i++ {
1459 fileResults := <-results
1460 if fileResults == nil {
1461 continue
1462 }
1463 hasErrors = hasErrors || len(fileResults.errs) > 0
1464 fileModified = fileModified || fileResults.modified
1465 for _, err := range fileResults.errs {
1466 fmt.Fprintf(opts.ErrWriter, "%s: %s\n", fileResults.file, err)
1467 }
1468 if fileResults.modified && !opts.Quiet {
1469 fmt.Fprintf(opts.ErrWriter, "fixed %s\n", fileResults.file)
1470 }
1471 if fileResults.records != nil {
1472 records = append(records, fileResults.records...)
1473 }
1474 }
1475
1476 if opts.IsPrintingProto {
1477 data, err := proto.Marshal(&apipb.Output{Records: records})
1478 if err != nil {
1479 log.Fatal("marshaling error: ", err)
1480 }
1481 fmt.Fprintf(opts.OutWriter, "%s", data)
1482 } else if opts.IsPrintingJSON {
1483 marshaler := jsonpb.Marshaler{}
1484 if err := marshaler.Marshal(opts.OutWriter, &apipb.Output{Records: records}); err != nil {
1485 log.Fatal("json marshaling error: ", err)
1486 }
1487 fmt.Fprintln(opts.OutWriter)
1488 } else {
1489 for _, record := range records {
1490 printRecord(opts.OutWriter, record)
1491 }
1492 }
1493
1494 if hasErrors {
1495 return 2
1496 }
1497 if fileModified || opts.Stdout {
1498 return 0
1499 }
1500
1501 nonReadonlyCommands := false
1502 for _, commandsByTarget := range commandsByFile {
1503 for _, commands := range commandsByTarget {
1504 for _, command := range commands.commands {
1505 if _, ok := readonlyCommands[command.tokens[0]]; !ok {
1506 nonReadonlyCommands = true
1507 break
1508 }
1509 }
1510 }
1511 }
1512 if nonReadonlyCommands {
1513 return 3
1514 }
1515 return 0
1516 }
1517
View as plain text