github.com/cheshirekow/buildtools@v0.0.0-20200224190056-5d637702fe81/edit/buildozer.go (about)

     1  /*
     2  Copyright 2016 Google Inc. All Rights Reserved.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10   See the License for the specific language governing permissions and
    11   limitations under the License.
    12  */
    13  
    14  // Buildozer is a tool for programmatically editing BUILD files.
    15  
    16  package edit
    17  
    18  import (
    19  	"bufio"
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"log"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"regexp"
    30  	"runtime"
    31  	"strconv"
    32  	"strings"
    33  
    34  	apipb "github.com/cheshirekow/buildtools/api_proto"
    35  	"github.com/cheshirekow/buildtools/build"
    36  	"github.com/cheshirekow/buildtools/file"
    37  	"github.com/golang/protobuf/proto"
    38  )
    39  
    40  // Options represents choices about how buildozer should behave.
    41  type Options struct {
    42  	Stdout            bool     // write changed BUILD file to stdout
    43  	Buildifier        string   // path to buildifier binary
    44  	Parallelism       int      // number of cores to use for concurrent actions
    45  	NumIO             int      // number of concurrent actions
    46  	CommandsFile      string   // file name to read commands from, use '-' for stdin (format:|-separated command line arguments to buildozer, excluding flags
    47  	KeepGoing         bool     // apply all commands, even if there are failures
    48  	FilterRuleTypes   []string // list of rule types to change, empty means all
    49  	PreferEOLComments bool     // when adding a new comment, put it on the same line if possible
    50  	RootDir           string   // If present, use this folder rather than $PWD to find the root dir
    51  	Quiet             bool     // suppress informational messages.
    52  	EditVariables     bool     // for attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.
    53  	IsPrintingProto   bool     // output serialized devtools.buildozer.Output protos instead of human-readable strings
    54  }
    55  
    56  // NewOpts returns a new Options struct with some defaults set.
    57  func NewOpts() *Options {
    58  	return &Options{NumIO: 200, PreferEOLComments: true}
    59  }
    60  
    61  // Usage is a user-overridden func to print the program usage.
    62  var Usage = func() {}
    63  
    64  var fileModified = false // set to true when a file has been fixed
    65  
    66  const stdinPackageName = "-" // the special package name to represent stdin
    67  
    68  // CmdEnvironment stores the information the commands below have access to.
    69  type CmdEnvironment struct {
    70  	File   *build.File                  // the AST
    71  	Rule   *build.Rule                  // the rule to modify
    72  	Vars   map[string]*build.AssignExpr // global variables set in the build file
    73  	Pkg    string                       // the full package name
    74  	Args   []string                     // the command-line arguments
    75  	output *apipb.Output_Record         // output proto, stores whatever a command wants to print
    76  }
    77  
    78  // The cmdXXX functions implement the various commands.
    79  
    80  func cmdAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
    81  	attr := env.Args[0]
    82  	for _, val := range env.Args[1:] {
    83  		if IsIntList(attr) {
    84  			AddValueToListAttribute(env.Rule, attr, env.Pkg, &build.LiteralExpr{Token: val}, &env.Vars)
    85  			continue
    86  		}
    87  		strVal := getStringExpr(val, env.Pkg)
    88  		AddValueToListAttribute(env.Rule, attr, env.Pkg, strVal, &env.Vars)
    89  	}
    90  	ResolveAttr(env.Rule, attr, env.Pkg)
    91  	return env.File, nil
    92  }
    93  
    94  func cmdComment(opts *Options, env CmdEnvironment) (*build.File, error) {
    95  	// The comment string is always the last argument in the list.
    96  	str := env.Args[len(env.Args)-1]
    97  	str = strings.Replace(str, "\\n", "\n", -1)
    98  	// Multiline comments should go on a separate line.
    99  	fullLine := !opts.PreferEOLComments || strings.Contains(str, "\n")
   100  	comment := []build.Comment{}
   101  	for _, line := range strings.Split(str, "\n") {
   102  		comment = append(comment, build.Comment{Token: "# " + line})
   103  	}
   104  
   105  	// The comment might be attached to a rule, an attribute, or a value in a list,
   106  	// depending on how many arguments are passed.
   107  	switch len(env.Args) {
   108  	case 1: // Attach to a rule
   109  		env.Rule.Call.Comments.Before = comment
   110  	case 2: // Attach to an attribute
   111  		if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
   112  			if fullLine {
   113  				attr.LHS.Comment().Before = comment
   114  			} else {
   115  				attr.RHS.Comment().Suffix = comment
   116  			}
   117  		}
   118  	case 3: // Attach to a specific value in a list
   119  		if attr := env.Rule.Attr(env.Args[0]); attr != nil {
   120  			if expr := ListFind(attr, env.Args[1], env.Pkg); expr != nil {
   121  				if fullLine {
   122  					expr.Comments.Before = comment
   123  				} else {
   124  					expr.Comments.Suffix = comment
   125  				}
   126  			}
   127  		}
   128  	default:
   129  		panic("cmdComment")
   130  	}
   131  	return env.File, nil
   132  }
   133  
   134  // commentsText concatenates comments into a single line.
   135  func commentsText(comments []build.Comment) string {
   136  	var segments []string
   137  	for _, comment := range comments {
   138  		token := comment.Token
   139  		if strings.HasPrefix(token, "#") {
   140  			token = token[1:]
   141  		}
   142  		segments = append(segments, strings.TrimSpace(token))
   143  	}
   144  	return strings.Replace(strings.Join(segments, " "), "\n", " ", -1)
   145  }
   146  
   147  func cmdPrintComment(opts *Options, env CmdEnvironment) (*build.File, error) {
   148  	attrError := func() error {
   149  		return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0])
   150  	}
   151  
   152  	switch len(env.Args) {
   153  	case 0: // Print rule comment.
   154  		env.output.Fields = []*apipb.Output_Record_Field{
   155  			{Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}},
   156  		}
   157  	case 1: // Print attribute comment.
   158  		attr := env.Rule.AttrDefn(env.Args[0])
   159  		if attr == nil {
   160  			return nil, attrError()
   161  		}
   162  		comments := append(attr.Before, attr.Suffix...)
   163  		env.output.Fields = []*apipb.Output_Record_Field{
   164  			{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
   165  		}
   166  	case 2: // Print comment of a specific value in a list.
   167  		attr := env.Rule.Attr(env.Args[0])
   168  		if attr == nil {
   169  			return nil, attrError()
   170  		}
   171  		value := env.Args[1]
   172  		expr := ListFind(attr, value, env.Pkg)
   173  		if expr == nil {
   174  			return nil, fmt.Errorf("attribute \"%s\" has no value \"%s\"", env.Args[0], value)
   175  		}
   176  		comments := append(expr.Comments.Before, expr.Comments.Suffix...)
   177  		env.output.Fields = []*apipb.Output_Record_Field{
   178  			{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
   179  		}
   180  	default:
   181  		panic("cmdPrintComment")
   182  	}
   183  	return nil, nil
   184  }
   185  
   186  func cmdDelete(opts *Options, env CmdEnvironment) (*build.File, error) {
   187  	return DeleteRule(env.File, env.Rule), nil
   188  }
   189  
   190  func cmdMove(opts *Options, env CmdEnvironment) (*build.File, error) {
   191  	oldAttr := env.Args[0]
   192  	newAttr := env.Args[1]
   193  	if len(env.Args) == 3 && env.Args[2] == "*" {
   194  		if err := MoveAllListAttributeValues(env.Rule, oldAttr, newAttr, env.Pkg, &env.Vars); err != nil {
   195  			return nil, err
   196  		}
   197  		return env.File, nil
   198  	}
   199  	fixed := false
   200  	for _, val := range env.Args[2:] {
   201  		if deleted := ListAttributeDelete(env.Rule, oldAttr, val, env.Pkg); deleted != nil {
   202  			AddValueToListAttribute(env.Rule, newAttr, env.Pkg, deleted, &env.Vars)
   203  			fixed = true
   204  		}
   205  	}
   206  	if fixed {
   207  		return env.File, nil
   208  	}
   209  	return nil, nil
   210  }
   211  
   212  func cmdNew(opts *Options, env CmdEnvironment) (*build.File, error) {
   213  	kind := env.Args[0]
   214  	name := env.Args[1]
   215  	addAtEOF, insertionIndex, err := findInsertionIndex(env)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if FindRuleByName(env.File, name) != nil {
   221  		return nil, fmt.Errorf("rule '%s' already exists", name)
   222  	}
   223  
   224  	call := &build.CallExpr{X: &build.Ident{Name: kind}}
   225  	rule := &build.Rule{call, ""}
   226  	rule.SetAttr("name", &build.StringExpr{Value: name})
   227  
   228  	if addAtEOF {
   229  		env.File.Stmt = InsertAfterLastOfSameKind(env.File.Stmt, rule.Call)
   230  	} else {
   231  		env.File.Stmt = InsertAfter(insertionIndex, env.File.Stmt, call)
   232  	}
   233  	return env.File, nil
   234  }
   235  
   236  // findInsertionIndex is used by cmdNew to find the place at which to insert the new rule.
   237  func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
   238  	if len(env.Args) < 4 {
   239  		return true, 0, nil
   240  	}
   241  
   242  	relativeToRuleName := env.Args[3]
   243  	ruleIdx, _ := IndexOfRuleByName(env.File, relativeToRuleName)
   244  	if ruleIdx == -1 {
   245  		return true, 0, nil
   246  	}
   247  
   248  	switch env.Args[2] {
   249  	case "before":
   250  		return false, ruleIdx - 1, nil
   251  	case "after":
   252  		return false, ruleIdx, nil
   253  	default:
   254  		return true, 0, fmt.Errorf("Unknown relative operator '%s'; allowed: 'before', 'after'", env.Args[1])
   255  	}
   256  }
   257  
   258  // splitLoadArgs splits arguments of form <[to=]from>
   259  // into a slice of froms and a slice of tos.
   260  func splitLoadArgs(args []string) ([]string, []string) {
   261  	from := args
   262  	to := append([]string{}, args...)
   263  	for i := range from {
   264  		if s := strings.SplitN(from[i], "=", 2); len(s) == 2 {
   265  			to[i] = s[0]
   266  			from[i] = s[1]
   267  		}
   268  	}
   269  
   270  	return from, to
   271  }
   272  
   273  func cmdNewLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
   274  	from, to := splitLoadArgs(env.Args[1:])
   275  	env.File.Stmt = InsertLoad(env.File.Stmt, env.Args[0], from, to)
   276  	return env.File, nil
   277  }
   278  
   279  func cmdReplaceLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
   280  	from, to := splitLoadArgs(env.Args[1:])
   281  	env.File.Stmt = ReplaceLoad(env.File.Stmt, env.Args[0], from, to)
   282  	return env.File, nil
   283  }
   284  
   285  func cmdSubstituteLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
   286  	oldRegexp, err := regexp.Compile(env.Args[0])
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	newTemplate := env.Args[1]
   291  
   292  	for _, stmt := range env.File.Stmt {
   293  		load, ok := stmt.(*build.LoadStmt)
   294  		if !ok {
   295  			continue
   296  		}
   297  
   298  		if newValue, ok := stringSubstitute(load.Module.Value, oldRegexp, newTemplate); ok {
   299  			load.Module.Value = newValue
   300  		}
   301  	}
   302  
   303  	return env.File, nil
   304  }
   305  
   306  func cmdPrint(opts *Options, env CmdEnvironment) (*build.File, error) {
   307  	format := env.Args
   308  	if len(format) == 0 {
   309  		format = []string{"name", "kind"}
   310  	}
   311  	fields := make([]*apipb.Output_Record_Field, len(format))
   312  
   313  	for i, str := range format {
   314  		value := env.Rule.Attr(str)
   315  		if str == "kind" {
   316  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Kind()}}
   317  		} else if str == "name" {
   318  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Name()}}
   319  		} else if str == "label" {
   320  			if env.Rule.Name() != "" {
   321  				fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{fmt.Sprintf("//%s:%s", env.Pkg, env.Rule.Name())}}
   322  			} else {
   323  				return nil, nil
   324  			}
   325  		} else if str == "rule" {
   326  			fields[i] = &apipb.Output_Record_Field{
   327  				Value: &apipb.Output_Record_Field_Text{build.FormatString(env.Rule.Call)},
   328  			}
   329  		} else if str == "startline" {
   330  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Number{int32(env.Rule.Call.ListStart.Line)}}
   331  		} else if str == "endline" {
   332  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Number{int32(env.Rule.Call.End.Pos.Line)}}
   333  		} else if value == nil {
   334  			fmt.Fprintf(os.Stderr, "rule \"//%s:%s\" has no attribute \"%s\"\n",
   335  				env.Pkg, env.Rule.Name(), str)
   336  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING}}
   337  		} else if lit, ok := value.(*build.LiteralExpr); ok {
   338  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{lit.Token}}
   339  		} else if lit, ok := value.(*build.Ident); ok {
   340  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{lit.Name}}
   341  		} else if string, ok := value.(*build.StringExpr); ok {
   342  			fields[i] = &apipb.Output_Record_Field{
   343  				Value:             &apipb.Output_Record_Field_Text{string.Value},
   344  				QuoteWhenPrinting: true,
   345  			}
   346  		} else if strList := env.Rule.AttrStrings(str); strList != nil {
   347  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_List{List: &apipb.RepeatedString{Strings: strList}}}
   348  		} else {
   349  			// Some other Expr we haven't listed above. Just print it.
   350  			fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{build.FormatString(value)}}
   351  		}
   352  	}
   353  
   354  	env.output.Fields = fields
   355  	return nil, nil
   356  }
   357  
   358  func attrKeysForPattern(rule *build.Rule, pattern string) []string {
   359  	if pattern == "*" {
   360  		return rule.AttrKeys()
   361  	}
   362  	return []string{pattern}
   363  }
   364  
   365  func cmdRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
   366  	if len(env.Args) == 1 { // Remove the attribute
   367  		if env.Rule.DelAttr(env.Args[0]) != nil {
   368  			return env.File, nil
   369  		}
   370  	} else { // Remove values in the attribute.
   371  		fixed := false
   372  		for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
   373  			for _, val := range env.Args[1:] {
   374  				ListAttributeDelete(env.Rule, key, val, env.Pkg)
   375  				fixed = true
   376  			}
   377  			ResolveAttr(env.Rule, key, env.Pkg)
   378  		}
   379  		if fixed {
   380  			return env.File, nil
   381  		}
   382  	}
   383  	return nil, nil
   384  }
   385  
   386  func cmdRemoveComment(opts *Options, env CmdEnvironment) (*build.File, error) {
   387  	switch len(env.Args) {
   388  	case 0: // Remove comment attached to rule
   389  		env.Rule.Call.Comments.Before = nil
   390  		env.Rule.Call.Comments.Suffix = nil
   391  		env.Rule.Call.Comments.After = nil
   392  	case 1: // Remove comment attached to attr
   393  		if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
   394  			attr.Comments.Before = nil
   395  			attr.Comments.Suffix = nil
   396  			attr.Comments.After = nil
   397  			attr.LHS.Comment().Before = nil
   398  			attr.LHS.Comment().Suffix = nil
   399  			attr.LHS.Comment().After = nil
   400  			attr.RHS.Comment().Before = nil
   401  			attr.RHS.Comment().Suffix = nil
   402  			attr.RHS.Comment().After = nil
   403  		}
   404  	case 2: // Remove comment attached to value
   405  		if attr := env.Rule.Attr(env.Args[0]); attr != nil {
   406  			if expr := ListFind(attr, env.Args[1], env.Pkg); expr != nil {
   407  				expr.Comments.Before = nil
   408  				expr.Comments.Suffix = nil
   409  				expr.Comments.After = nil
   410  			}
   411  		}
   412  	default:
   413  		panic("cmdRemoveComment")
   414  	}
   415  	return env.File, nil
   416  }
   417  
   418  func cmdRename(opts *Options, env CmdEnvironment) (*build.File, error) {
   419  	oldAttr := env.Args[0]
   420  	newAttr := env.Args[1]
   421  	if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil {
   422  		return nil, err
   423  	}
   424  	return env.File, nil
   425  }
   426  
   427  func cmdReplace(opts *Options, env CmdEnvironment) (*build.File, error) {
   428  	oldV := env.Args[1]
   429  	newV := env.Args[2]
   430  	for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
   431  		attr := env.Rule.Attr(key)
   432  		if e, ok := attr.(*build.StringExpr); ok {
   433  			if LabelsEqual(e.Value, oldV, env.Pkg) {
   434  				env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newV}, env))
   435  			}
   436  		} else {
   437  			ListReplace(attr, oldV, newV, env.Pkg)
   438  		}
   439  	}
   440  	return env.File, nil
   441  }
   442  
   443  func cmdSubstitute(opts *Options, env CmdEnvironment) (*build.File, error) {
   444  	oldRegexp, err := regexp.Compile(env.Args[1])
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	newTemplate := env.Args[2]
   449  	for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
   450  		attr := env.Rule.Attr(key)
   451  		e, ok := attr.(*build.StringExpr)
   452  		if !ok {
   453  			ListSubstitute(attr, oldRegexp, newTemplate)
   454  			continue
   455  		}
   456  		if newValue, ok := stringSubstitute(e.Value, oldRegexp, newTemplate); ok {
   457  			env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newValue}, env))
   458  		}
   459  	}
   460  	return env.File, nil
   461  }
   462  
   463  func cmdSet(opts *Options, env CmdEnvironment) (*build.File, error) {
   464  	attr := env.Args[0]
   465  	args := env.Args[1:]
   466  	if attr == "kind" {
   467  		env.Rule.SetKind(args[0])
   468  	} else {
   469  		env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
   470  	}
   471  	return env.File, nil
   472  }
   473  
   474  func cmdSetIfAbsent(opts *Options, env CmdEnvironment) (*build.File, error) {
   475  	attr := env.Args[0]
   476  	args := env.Args[1:]
   477  	if attr == "kind" {
   478  		return nil, fmt.Errorf("setting 'kind' is not allowed for set_if_absent. Got %s", env.Args)
   479  	}
   480  	if env.Rule.Attr(attr) == nil {
   481  		env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
   482  	}
   483  	return env.File, nil
   484  }
   485  
   486  func getAttrValueExpr(attr string, args []string, env CmdEnvironment) build.Expr {
   487  	switch {
   488  	case attr == "kind":
   489  		return nil
   490  	case IsIntList(attr):
   491  		var list []build.Expr
   492  		for _, i := range args {
   493  			list = append(list, &build.LiteralExpr{Token: i})
   494  		}
   495  		return &build.ListExpr{List: list}
   496  	case IsList(attr) && !(len(args) == 1 && strings.HasPrefix(args[0], "glob(")):
   497  		var list []build.Expr
   498  		for _, arg := range args {
   499  			list = append(list, getStringExpr(arg, env.Pkg))
   500  		}
   501  		return &build.ListExpr{List: list}
   502  	case len(args) == 0:
   503  		// Expected a non-list argument, nothing provided
   504  		return &build.Ident{Name: "None"}
   505  	case IsString(attr):
   506  		return getStringExpr(args[0], env.Pkg)
   507  	default:
   508  		return &build.Ident{Name: args[0]}
   509  	}
   510  }
   511  
   512  func getStringExpr(value, pkg string) build.Expr {
   513  	unquoted, triple, err := build.Unquote(value)
   514  	if err == nil {
   515  		return &build.StringExpr{Value: ShortenLabel(unquoted, pkg), TripleQuote: triple}
   516  	}
   517  	return &build.StringExpr{Value: ShortenLabel(value, pkg)}
   518  }
   519  
   520  func cmdCopy(opts *Options, env CmdEnvironment) (*build.File, error) {
   521  	attrName := env.Args[0]
   522  	from := env.Args[1]
   523  
   524  	return copyAttributeBetweenRules(env, attrName, from)
   525  }
   526  
   527  func cmdCopyNoOverwrite(opts *Options, env CmdEnvironment) (*build.File, error) {
   528  	attrName := env.Args[0]
   529  	from := env.Args[1]
   530  
   531  	if env.Rule.Attr(attrName) != nil {
   532  		return env.File, nil
   533  	}
   534  
   535  	return copyAttributeBetweenRules(env, attrName, from)
   536  }
   537  
   538  // cmdDictAdd adds a key to a dict, if that key does _not_ exit already.
   539  func cmdDictAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
   540  	attr := env.Args[0]
   541  	args := env.Args[1:]
   542  
   543  	dict := &build.DictExpr{}
   544  	currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
   545  	if ok {
   546  		dict = currDict
   547  	}
   548  
   549  	for _, x := range args {
   550  		kv := strings.SplitN(x, ":", 2)
   551  		expr := getStringExpr(kv[1], env.Pkg)
   552  
   553  		prev := DictionaryGet(dict, kv[0])
   554  		if prev == nil {
   555  			// Only set the value if the value is currently unset.
   556  			DictionarySet(dict, kv[0], expr)
   557  		}
   558  	}
   559  	env.Rule.SetAttr(attr, dict)
   560  	return env.File, nil
   561  }
   562  
   563  // cmdDictSet adds a key to a dict, overwriting any previous values.
   564  func cmdDictSet(opts *Options, env CmdEnvironment) (*build.File, error) {
   565  	attr := env.Args[0]
   566  	args := env.Args[1:]
   567  
   568  	dict := &build.DictExpr{}
   569  	currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
   570  	if ok {
   571  		dict = currDict
   572  	}
   573  
   574  	for _, x := range args {
   575  		kv := strings.SplitN(x, ":", 2)
   576  		expr := getStringExpr(kv[1], env.Pkg)
   577  		// Set overwrites previous values.
   578  		DictionarySet(dict, kv[0], expr)
   579  	}
   580  	env.Rule.SetAttr(attr, dict)
   581  	return env.File, nil
   582  }
   583  
   584  // cmdDictRemove removes a key from a dict.
   585  func cmdDictRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
   586  	attr := env.Args[0]
   587  	args := env.Args[1:]
   588  
   589  	thing := env.Rule.Attr(attr)
   590  	dictAttr, ok := thing.(*build.DictExpr)
   591  	if !ok {
   592  		return env.File, nil
   593  	}
   594  
   595  	for _, x := range args {
   596  		// should errors here be flagged?
   597  		DictionaryDelete(dictAttr, x)
   598  		env.Rule.SetAttr(attr, dictAttr)
   599  	}
   600  
   601  	// If the removal results in the dict having no contents, delete the attribute (stay clean!)
   602  	if dictAttr == nil || len(dictAttr.List) == 0 {
   603  		env.Rule.DelAttr(attr)
   604  	}
   605  
   606  	return env.File, nil
   607  }
   608  
   609  // cmdDictListAdd adds an item to a list in a dict.
   610  func cmdDictListAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
   611  	attr := env.Args[0]
   612  	key := env.Args[1]
   613  	args := env.Args[2:]
   614  
   615  	dict := &build.DictExpr{}
   616  	if currDict, ok := env.Rule.Attr(attr).(*build.DictExpr); ok {
   617  		dict = currDict
   618  	}
   619  
   620  	prev := DictionaryGet(dict, key)
   621  	if prev == nil {
   622  		prev = &build.ListExpr{}
   623  	}
   624  
   625  	for _, val := range args {
   626  		expr := getStringExpr(val, env.Pkg)
   627  		prev = AddValueToList(prev, env.Pkg, expr, true)
   628  	}
   629  
   630  	DictionarySet(dict, key, prev)
   631  	env.Rule.SetAttr(attr, dict)
   632  
   633  	return env.File, nil
   634  }
   635  
   636  func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string) (*build.File, error) {
   637  	fromRule := FindRuleByName(env.File, from)
   638  	if fromRule == nil {
   639  		return nil, fmt.Errorf("could not find rule '%s'", from)
   640  	}
   641  	attr := fromRule.Attr(attrName)
   642  	if attr == nil {
   643  		return nil, fmt.Errorf("rule '%s' does not have attribute '%s'", from, attrName)
   644  	}
   645  
   646  	ast, err := build.ParseBuild("" /* filename */, []byte(build.FormatString(attr)))
   647  	if err != nil {
   648  		return nil, fmt.Errorf("could not parse attribute value %v", build.FormatString(attr))
   649  	}
   650  
   651  	env.Rule.SetAttr(attrName, ast.Stmt[0])
   652  	return env.File, nil
   653  }
   654  
   655  func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
   656  	// Fix the whole file
   657  	if env.Rule.Kind() == "package" {
   658  		return FixFile(env.File, env.Pkg, env.Args), nil
   659  	}
   660  	// Fix a specific rule
   661  	return FixRule(env.File, env.Pkg, env.Rule, env.Args), nil
   662  }
   663  
   664  // CommandInfo provides a command function and info on incoming arguments.
   665  type CommandInfo struct {
   666  	Fn       func(*Options, CmdEnvironment) (*build.File, error)
   667  	PerRule  bool
   668  	MinArg   int
   669  	MaxArg   int
   670  	Template string
   671  }
   672  
   673  // AllCommands associates the command names with their function and number
   674  // of arguments.
   675  var AllCommands = map[string]CommandInfo{
   676  	"add":               {cmdAdd, true, 2, -1, "<attr> <value(s)>"},
   677  	"new_load":          {cmdNewLoad, false, 1, -1, "<path> <[to=]from(s)>"},
   678  	"replace_load":      {cmdReplaceLoad, false, 1, -1, "<path> <[to=]symbol(s)>"},
   679  	"substitute_load":   {cmdSubstituteLoad, false, 2, 2, "<old_regexp> <new_template>"},
   680  	"comment":           {cmdComment, true, 1, 3, "<attr>? <value>? <comment>"},
   681  	"print_comment":     {cmdPrintComment, true, 0, 2, "<attr>? <value>?"},
   682  	"delete":            {cmdDelete, true, 0, 0, ""},
   683  	"fix":               {cmdFix, true, 0, -1, "<fix(es)>?"},
   684  	"move":              {cmdMove, true, 3, -1, "<old_attr> <new_attr> <value(s)>"},
   685  	"new":               {cmdNew, false, 2, 4, "<rule_kind> <rule_name> [(before|after) <relative_rule_name>]"},
   686  	"print":             {cmdPrint, true, 0, -1, "<attribute(s)>"},
   687  	"remove":            {cmdRemove, true, 1, -1, "<attr> <value(s)>"},
   688  	"remove_comment":    {cmdRemoveComment, true, 0, 2, "<attr>? <value>?"},
   689  	"rename":            {cmdRename, true, 2, 2, "<old_attr> <new_attr>"},
   690  	"replace":           {cmdReplace, true, 3, 3, "<attr> <old_value> <new_value>"},
   691  	"substitute":        {cmdSubstitute, true, 3, 3, "<attr> <old_regexp> <new_template>"},
   692  	"set":               {cmdSet, true, 1, -1, "<attr> <value(s)>"},
   693  	"set_if_absent":     {cmdSetIfAbsent, true, 1, -1, "<attr> <value(s)>"},
   694  	"copy":              {cmdCopy, true, 2, 2, "<attr> <from_rule>"},
   695  	"copy_no_overwrite": {cmdCopyNoOverwrite, true, 2, 2, "<attr> <from_rule>"},
   696  	"dict_add":          {cmdDictAdd, true, 2, -1, "<attr> <(key:value)(s)>"},
   697  	"dict_set":          {cmdDictSet, true, 2, -1, "<attr> <(key:value)(s)>"},
   698  	"dict_remove":       {cmdDictRemove, true, 2, -1, "<attr> <key(s)>"},
   699  	"dict_list_add":     {cmdDictListAdd, true, 3, -1, "<attr> <key> <value(s)>"},
   700  }
   701  
   702  func expandTargets(f *build.File, rule string) ([]*build.Rule, error) {
   703  	if r := FindRuleByName(f, rule); r != nil {
   704  		return []*build.Rule{r}, nil
   705  	} else if r := FindExportedFile(f, rule); r != nil {
   706  		return []*build.Rule{r}, nil
   707  	} else if rule == "all" || rule == "*" {
   708  		// "all" is a valid name, it is a wildcard only if no such rule is found.
   709  		return f.Rules(""), nil
   710  	} else if strings.HasPrefix(rule, "%") {
   711  		// "%java_library" will match all java_library functions in the package
   712  		// "%<LINENUM>" will match the rule which begins at LINENUM.
   713  		// This is for convenience, "%" is not a valid character in bazel targets.
   714  		kind := rule[1:]
   715  		if linenum, err := strconv.Atoi(kind); err == nil {
   716  			if r := f.RuleAt(linenum); r != nil {
   717  				return []*build.Rule{r}, nil
   718  			}
   719  		} else {
   720  			return f.Rules(kind), nil
   721  		}
   722  	}
   723  	return nil, fmt.Errorf("rule '%s' not found", rule)
   724  }
   725  
   726  func filterRules(opts *Options, rules []*build.Rule) (result []*build.Rule) {
   727  	if len(opts.FilterRuleTypes) == 0 {
   728  		return rules
   729  	}
   730  	for _, rule := range rules {
   731  		for _, filterType := range opts.FilterRuleTypes {
   732  			if rule.Kind() == filterType {
   733  				result = append(result, rule)
   734  				break
   735  			}
   736  		}
   737  	}
   738  	return
   739  }
   740  
   741  // command contains a list of tokens that describe a buildozer command.
   742  type command struct {
   743  	tokens []string
   744  }
   745  
   746  // checkCommandUsage checks the number of argument of a command.
   747  // It prints an error and usage when it is not valid.
   748  func checkCommandUsage(name string, cmd CommandInfo, count int) {
   749  	if count >= cmd.MinArg && (cmd.MaxArg == -1 || count <= cmd.MaxArg) {
   750  		return
   751  	}
   752  
   753  	if count < cmd.MinArg {
   754  		fmt.Fprintf(os.Stderr, "Too few arguments for command '%s', expected at least %d.\n",
   755  			name, cmd.MinArg)
   756  	} else {
   757  		fmt.Fprintf(os.Stderr, "Too many arguments for command '%s', expected at most %d.\n",
   758  			name, cmd.MaxArg)
   759  	}
   760  	Usage()
   761  	os.Exit(1)
   762  }
   763  
   764  // Match text that only contains spaces or line breaks if they're escaped with '\'.
   765  var spaceRegex = regexp.MustCompile(`(\\ |\\\n|[^ \n])+`)
   766  
   767  // SplitOnSpaces behaves like strings.Fields, except that spaces can be escaped.
   768  // Also splits on linebreaks unless they are escaped too.
   769  // " some dummy\\ string" -> ["some", "dummy string"]
   770  func SplitOnSpaces(input string) []string {
   771  	result := spaceRegex.FindAllString(input, -1)
   772  	for i, s := range result {
   773  		s = strings.Replace(s, `\ `, " ", -1)
   774  		s = strings.Replace(s, "\\\n", "\n", -1)
   775  		result[i] = s
   776  	}
   777  	return result
   778  }
   779  
   780  // parseCommands parses commands and targets they should be applied on from
   781  // a list of arguments.
   782  // Each argument can be either:
   783  // - a command (as defined by AllCommands) and its parameters, separated by
   784  //   whitespace
   785  // - a target all commands that are parsed during one call to parseCommands
   786  //   should be applied on
   787  func parseCommands(args []string) (commands []command, targets []string) {
   788  	for _, arg := range args {
   789  		commandTokens := SplitOnSpaces(arg)
   790  		cmd, found := AllCommands[commandTokens[0]]
   791  		if found {
   792  			checkCommandUsage(commandTokens[0], cmd, len(commandTokens)-1)
   793  			commands = append(commands, command{commandTokens})
   794  		} else {
   795  			targets = append(targets, arg)
   796  		}
   797  	}
   798  	return
   799  }
   800  
   801  // commandsForTarget contains commands to be executed on the given target.
   802  type commandsForTarget struct {
   803  	target   string
   804  	commands []command
   805  }
   806  
   807  // commandsForFile contains the file name and all commands that should be
   808  // applied on that file, indexed by their target.
   809  type commandsForFile struct {
   810  	file     string
   811  	commands []commandsForTarget
   812  }
   813  
   814  // commandError returns an error that formats 'err' in the context of the
   815  // commands to be executed on the given target.
   816  func commandError(commands []command, target string, err error) error {
   817  	return fmt.Errorf("error while executing commands %s on target %s: %s", commands, target, err)
   818  }
   819  
   820  // rewriteResult contains the outcome of applying fixes to a single file.
   821  type rewriteResult struct {
   822  	file     string
   823  	errs     []error
   824  	modified bool
   825  	records  []*apipb.Output_Record
   826  }
   827  
   828  // getGlobalVariables returns the global variable assignments in the provided list of expressions.
   829  // That is, for each variable assignment of the form
   830  //   a = v
   831  // vars["a"] will contain the AssignExpr whose RHS value is the assignment "a = v".
   832  func getGlobalVariables(exprs []build.Expr) (vars map[string]*build.AssignExpr) {
   833  	vars = make(map[string]*build.AssignExpr)
   834  	for _, expr := range exprs {
   835  		if as, ok := expr.(*build.AssignExpr); ok {
   836  			if lhs, ok := as.LHS.(*build.Ident); ok {
   837  				vars[lhs.Name] = as
   838  			}
   839  		}
   840  	}
   841  	return vars
   842  }
   843  
   844  // When checking the filesystem, we need to look for any of the
   845  // possible buildFileNames. For historical reasons, the
   846  // parts of the tool that generate paths that we may want to examine
   847  // continue to assume that build files are all named "BUILD".
   848  var buildFileNames = [...]string{"BUILD.bazel", "BUILD", "BUCK"}
   849  var buildFileNamesSet = map[string]bool{
   850  	"BUILD.bazel": true,
   851  	"BUILD":       true,
   852  	"BUCK":        true,
   853  }
   854  
   855  // rewrite parses the BUILD file for the given file, transforms the AST,
   856  // and write the changes back in the file (or on stdout).
   857  func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
   858  	name := commandsForFile.file
   859  	var data []byte
   860  	var err error
   861  	var fi os.FileInfo
   862  	records := []*apipb.Output_Record{}
   863  	if name == stdinPackageName { // read on stdin
   864  		data, err = ioutil.ReadAll(os.Stdin)
   865  		if err != nil {
   866  			return &rewriteResult{file: name, errs: []error{err}}
   867  		}
   868  	} else {
   869  		origName := name
   870  		for _, suffix := range buildFileNames {
   871  			if strings.HasSuffix(name, "/"+suffix) {
   872  				name = strings.TrimSuffix(name, suffix)
   873  				break
   874  			}
   875  		}
   876  		for _, suffix := range buildFileNames {
   877  			name = name + suffix
   878  			data, fi, err = file.ReadFile(name)
   879  			if err == nil {
   880  				break
   881  			}
   882  			name = strings.TrimSuffix(name, suffix)
   883  		}
   884  		if err != nil {
   885  			data, fi, err = file.ReadFile(name)
   886  		}
   887  		if err != nil {
   888  			err = errors.New("file not found or not readable")
   889  			return &rewriteResult{file: origName, errs: []error{err}}
   890  		}
   891  	}
   892  
   893  	f, err := build.ParseBuild(name, data)
   894  	if err != nil {
   895  		return &rewriteResult{file: name, errs: []error{err}}
   896  	}
   897  
   898  	vars := map[string]*build.AssignExpr{}
   899  	if opts.EditVariables {
   900  		vars = getGlobalVariables(f.Stmt)
   901  	}
   902  	var errs []error
   903  	changed := false
   904  	for _, commands := range commandsForFile.commands {
   905  		target := commands.target
   906  		commands := commands.commands
   907  		_, absPkg, rule := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
   908  		_, pkg, _ := ParseLabel(target)
   909  		if pkg == stdinPackageName { // Special-case: This is already absolute
   910  			absPkg = stdinPackageName
   911  		}
   912  
   913  		targets, err := expandTargets(f, rule)
   914  		if err != nil {
   915  			cerr := commandError(commands, target, err)
   916  			errs = append(errs, cerr)
   917  			if !opts.KeepGoing {
   918  				return &rewriteResult{file: name, errs: errs, records: records}
   919  
   920  			}
   921  		}
   922  		targets = filterRules(opts, targets)
   923  		for _, cmd := range commands {
   924  			cmdInfo := AllCommands[cmd.tokens[0]]
   925  			// Depending on whether a transformation is rule-specific or not, it should be applied to
   926  			// every rule that satisfies the filter or just once to the file.
   927  			cmdTargets := targets
   928  			if !cmdInfo.PerRule {
   929  				cmdTargets = []*build.Rule{nil}
   930  			}
   931  			for _, r := range cmdTargets {
   932  				record := &apipb.Output_Record{}
   933  				newf, err := cmdInfo.Fn(opts, CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
   934  				if len(record.Fields) != 0 {
   935  					records = append(records, record)
   936  				}
   937  				if err != nil {
   938  					cerr := commandError([]command{cmd}, target, err)
   939  					if opts.KeepGoing {
   940  						errs = append(errs, cerr)
   941  					} else {
   942  						return &rewriteResult{file: name, errs: []error{cerr}, records: records}
   943  					}
   944  				}
   945  				if newf != nil {
   946  					changed = true
   947  					f = newf
   948  				}
   949  			}
   950  		}
   951  	}
   952  	if !changed {
   953  		return &rewriteResult{file: name, errs: errs, records: records}
   954  	}
   955  	f = RemoveEmptyPackage(f)
   956  	ndata, err := runBuildifier(opts, f)
   957  	if err != nil {
   958  		return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
   959  	}
   960  
   961  	if opts.Stdout || name == stdinPackageName {
   962  		os.Stdout.Write(ndata)
   963  		return &rewriteResult{file: name, errs: errs, records: records}
   964  	}
   965  
   966  	if bytes.Equal(data, ndata) {
   967  		return &rewriteResult{file: name, errs: errs, records: records}
   968  	}
   969  
   970  	if err := EditFile(fi, name); err != nil {
   971  		return &rewriteResult{file: name, errs: []error{err}, records: records}
   972  	}
   973  
   974  	if err := file.WriteFile(name, ndata); err != nil {
   975  		return &rewriteResult{file: name, errs: []error{err}, records: records}
   976  	}
   977  
   978  	fileModified = true
   979  	return &rewriteResult{file: name, errs: errs, modified: true, records: records}
   980  }
   981  
   982  // EditFile is a function that does any prework needed before editing a file.
   983  // e.g. "checking out for write" from a locking source control repo.
   984  var EditFile = func(fi os.FileInfo, name string) error {
   985  	return nil
   986  }
   987  
   988  // runBuildifier formats the build file f.
   989  // Runs opts.Buildifier if it's non-empty, otherwise uses built-in formatter.
   990  // opts.Buildifier is useful to force consistency with other tools that call Buildifier.
   991  func runBuildifier(opts *Options, f *build.File) ([]byte, error) {
   992  	if opts.Buildifier == "" {
   993  		build.Rewrite(f, nil)
   994  		return build.Format(f), nil
   995  	}
   996  
   997  	cmd := exec.Command(opts.Buildifier, "--type=build")
   998  	data := build.Format(f)
   999  	cmd.Stdin = bytes.NewBuffer(data)
  1000  	stdout := bytes.NewBuffer(nil)
  1001  	stderr := bytes.NewBuffer(nil)
  1002  	cmd.Stdout = stdout
  1003  	cmd.Stderr = stderr
  1004  	err := cmd.Run()
  1005  	if stderr.Len() > 0 {
  1006  		return nil, fmt.Errorf("%s", stderr.Bytes())
  1007  	}
  1008  	if err != nil {
  1009  		return nil, err
  1010  	}
  1011  	return stdout.Bytes(), nil
  1012  }
  1013  
  1014  // Given a target, whose package may contain a trailing "/...", returns all
  1015  // existing BUILD file paths which match the package.
  1016  func targetExpressionToBuildFiles(rootDir string, target string) []string {
  1017  	file, _, _ := InterpretLabelForWorkspaceLocation(rootDir, target)
  1018  	if rootDir == "" {
  1019  		var err error
  1020  		if file, err = filepath.Abs(file); err != nil {
  1021  			fmt.Printf("Cannot make path absolute: %s\n", err.Error())
  1022  			os.Exit(1)
  1023  		}
  1024  	}
  1025  
  1026  	suffix := filepath.Join("", "...", "BUILD") // /.../BUILD
  1027  	if !strings.HasSuffix(file, suffix) {
  1028  		return []string{file}
  1029  	}
  1030  
  1031  	return findBuildFiles(strings.TrimSuffix(file, suffix))
  1032  }
  1033  
  1034  // Given a root directory, returns all "BUILD" files in that subtree recursively.
  1035  func findBuildFiles(rootDir string) []string {
  1036  	var buildFiles []string
  1037  	searchDirs := []string{rootDir}
  1038  
  1039  	for len(searchDirs) != 0 {
  1040  		lastIndex := len(searchDirs) - 1
  1041  		dir := searchDirs[lastIndex]
  1042  		searchDirs = searchDirs[:lastIndex]
  1043  
  1044  		dirFiles, err := ioutil.ReadDir(dir)
  1045  		if err != nil {
  1046  			continue
  1047  		}
  1048  
  1049  		for _, dirFile := range dirFiles {
  1050  			if dirFile.IsDir() {
  1051  				searchDirs = append(searchDirs, filepath.Join(dir, dirFile.Name()))
  1052  			} else if _, ok := buildFileNamesSet[dirFile.Name()]; ok {
  1053  				buildFiles = append(buildFiles, filepath.Join(dir, dirFile.Name()))
  1054  			}
  1055  		}
  1056  	}
  1057  
  1058  	return buildFiles
  1059  }
  1060  
  1061  // appendCommands adds the given commands to be applied to each of the given targets
  1062  // via the commandMap.
  1063  func appendCommands(opts *Options, commandMap map[string][]commandsForTarget, args []string) {
  1064  	commands, targets := parseCommands(args)
  1065  	for _, target := range targets {
  1066  		for _, buildFileName := range buildFileNames {
  1067  			if strings.HasSuffix(target, filepath.FromSlash("/"+buildFileName)) {
  1068  				target = strings.TrimSuffix(target, filepath.FromSlash("/"+buildFileName)) + ":__pkg__"
  1069  			}
  1070  		}
  1071  		var buildFiles []string
  1072  		_, pkg, _ := ParseLabel(target)
  1073  		if pkg == stdinPackageName {
  1074  			buildFiles = []string{stdinPackageName}
  1075  		} else {
  1076  			buildFiles = targetExpressionToBuildFiles(opts.RootDir, target)
  1077  		}
  1078  
  1079  		for _, file := range buildFiles {
  1080  			commandMap[file] = append(commandMap[file], commandsForTarget{target, commands})
  1081  		}
  1082  	}
  1083  }
  1084  
  1085  func appendCommandsFromFile(opts *Options, commandsByFile map[string][]commandsForTarget, fileName string) {
  1086  	var reader io.Reader
  1087  	if opts.CommandsFile == stdinPackageName {
  1088  		reader = os.Stdin
  1089  	} else {
  1090  		rc := file.OpenReadFile(opts.CommandsFile)
  1091  		reader = rc
  1092  		defer rc.Close()
  1093  	}
  1094  	appendCommandsFromReader(opts, reader, commandsByFile)
  1095  }
  1096  
  1097  func appendCommandsFromReader(opts *Options, reader io.Reader, commandsByFile map[string][]commandsForTarget) {
  1098  	r := bufio.NewReader(reader)
  1099  	atEOF := false
  1100  	for !atEOF {
  1101  		line, err := r.ReadString('\n')
  1102  		if err == io.EOF {
  1103  			atEOF = true
  1104  			err = nil
  1105  		}
  1106  		if err != nil {
  1107  			fmt.Fprintf(os.Stderr, "Error while reading commands file: %v", err)
  1108  			return
  1109  		}
  1110  		line = strings.TrimSuffix(line, "\n")
  1111  		if line == "" {
  1112  			continue
  1113  		}
  1114  		args := strings.Split(line, "|")
  1115  		appendCommands(opts, commandsByFile, args)
  1116  	}
  1117  }
  1118  
  1119  func printRecord(writer io.Writer, record *apipb.Output_Record) {
  1120  	fields := record.Fields
  1121  	line := make([]string, len(fields))
  1122  	for i, field := range fields {
  1123  		switch value := field.Value.(type) {
  1124  		case *apipb.Output_Record_Field_Text:
  1125  			if field.QuoteWhenPrinting && strings.ContainsRune(value.Text, ' ') {
  1126  				line[i] = fmt.Sprintf("%q", value.Text)
  1127  			} else {
  1128  				line[i] = value.Text
  1129  			}
  1130  			break
  1131  		case *apipb.Output_Record_Field_Number:
  1132  			line[i] = strconv.Itoa(int(value.Number))
  1133  			break
  1134  		case *apipb.Output_Record_Field_Error:
  1135  			switch value.Error {
  1136  			case apipb.Output_Record_Field_UNKNOWN:
  1137  				line[i] = "(unknown)"
  1138  				break
  1139  			case apipb.Output_Record_Field_MISSING:
  1140  				line[i] = "(missing)"
  1141  				break
  1142  			}
  1143  			break
  1144  		case *apipb.Output_Record_Field_List:
  1145  			line[i] = fmt.Sprintf("[%s]", strings.Join(value.List.Strings, " "))
  1146  			break
  1147  		}
  1148  	}
  1149  
  1150  	fmt.Fprint(writer, strings.Join(line, " ")+"\n")
  1151  }
  1152  
  1153  // Buildozer loops over all arguments on the command line fixing BUILD files.
  1154  func Buildozer(opts *Options, args []string) int {
  1155  	commandsByFile := make(map[string][]commandsForTarget)
  1156  	if opts.CommandsFile != "" {
  1157  		appendCommandsFromFile(opts, commandsByFile, opts.CommandsFile)
  1158  	} else {
  1159  		if len(args) == 0 {
  1160  			Usage()
  1161  		}
  1162  		appendCommands(opts, commandsByFile, args)
  1163  	}
  1164  
  1165  	numFiles := len(commandsByFile)
  1166  	if opts.Parallelism > 0 {
  1167  		runtime.GOMAXPROCS(opts.Parallelism)
  1168  	}
  1169  	results := make(chan *rewriteResult, numFiles)
  1170  	data := make(chan commandsForFile)
  1171  
  1172  	for i := 0; i < opts.NumIO; i++ {
  1173  		go func(results chan *rewriteResult, data chan commandsForFile) {
  1174  			for commandsForFile := range data {
  1175  				results <- rewrite(opts, commandsForFile)
  1176  			}
  1177  		}(results, data)
  1178  	}
  1179  
  1180  	for file, commands := range commandsByFile {
  1181  		data <- commandsForFile{file, commands}
  1182  	}
  1183  	close(data)
  1184  	records := []*apipb.Output_Record{}
  1185  	hasErrors := false
  1186  	for i := 0; i < numFiles; i++ {
  1187  		fileResults := <-results
  1188  		if fileResults == nil {
  1189  			continue
  1190  		}
  1191  		hasErrors = hasErrors || len(fileResults.errs) > 0
  1192  		for _, err := range fileResults.errs {
  1193  			fmt.Fprintf(os.Stderr, "%s: %s\n", fileResults.file, err)
  1194  		}
  1195  		if fileResults.modified && !opts.Quiet {
  1196  			fmt.Fprintf(os.Stderr, "fixed %s\n", fileResults.file)
  1197  		}
  1198  		if fileResults.records != nil {
  1199  			records = append(records, fileResults.records...)
  1200  		}
  1201  	}
  1202  
  1203  	if opts.IsPrintingProto {
  1204  		data, err := proto.Marshal(&apipb.Output{Records: records})
  1205  		if err != nil {
  1206  			log.Fatal("marshaling error: ", err)
  1207  		}
  1208  		fmt.Fprintf(os.Stdout, "%s", data)
  1209  	} else {
  1210  		for _, record := range records {
  1211  			printRecord(os.Stdout, record)
  1212  		}
  1213  	}
  1214  
  1215  	if hasErrors {
  1216  		return 2
  1217  	}
  1218  	if !fileModified && !opts.Stdout {
  1219  		return 3
  1220  	}
  1221  	return 0
  1222  }