...

Source file src/github.com/bazelbuild/buildtools/edit/buildozer.go

Documentation: github.com/bazelbuild/buildtools/edit

     1  /*
     2  Copyright 2016 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Buildozer is a tool for programmatically editing BUILD files.
    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  // Options represents choices about how buildozer should behave.
    47  type Options struct {
    48  	Stdout            bool      // write changed BUILD file to stdout
    49  	Buildifier        string    // path to buildifier binary
    50  	Parallelism       int       // number of cores to use for concurrent actions
    51  	NumIO             int       // number of concurrent actions
    52  	CommandsFiles     []string  // file names to read commands from, use '-' for stdin (format:|-separated command line arguments to buildozer, excluding flags
    53  	KeepGoing         bool      // apply all commands, even if there are failures
    54  	FilterRuleTypes   []string  // list of rule types to change, empty means all
    55  	PreferEOLComments bool      // when adding a new comment, put it on the same line if possible
    56  	RootDir           string    // If present, use this folder rather than $PWD to find the root dir
    57  	Quiet             bool      // suppress informational messages.
    58  	EditVariables     bool      // for attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.
    59  	IsPrintingProto   bool      // output serialized devtools.buildozer.Output protos instead of human-readable strings
    60  	IsPrintingJSON    bool      // output serialized devtools.buildozer.Output json instead of human-readable strings
    61  	OutWriter         io.Writer // where to write normal output (`os.Stdout` will be used if not specified)
    62  	ErrWriter         io.Writer // where to write error output (`os.Stderr` will be used if not specified)
    63  }
    64  
    65  // NewOpts returns a new Options struct with some defaults set.
    66  func NewOpts() *Options {
    67  	return &Options{NumIO: 200, PreferEOLComments: true}
    68  }
    69  
    70  // Usage is a user-overridden func to print the program usage.
    71  var Usage = func() {}
    72  
    73  const stdinPackageName = "-" // the special package name to represent stdin
    74  
    75  // CmdEnvironment stores the information the commands below have access to.
    76  type CmdEnvironment struct {
    77  	File   *build.File                  // the AST
    78  	Rule   *build.Rule                  // the rule to modify
    79  	Vars   map[string]*build.AssignExpr // global variables set in the build file
    80  	Pkg    string                       // the full package name
    81  	Args   []string                     // the command-line arguments
    82  	output *apipb.Output_Record         // output proto, stores whatever a command wants to print
    83  }
    84  
    85  // The cmdXXX functions implement the various commands.
    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  	// The comment string is always the last argument in the list.
   103  	str := env.Args[len(env.Args)-1]
   104  	str = strings.Replace(str, "\\n", "\n", -1)
   105  	// Multiline comments should go on a separate line.
   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  	// The comment might be attached to a rule, an attribute, or a value in a list,
   113  	// depending on how many arguments are passed.
   114  	switch len(env.Args) {
   115  	case 1: // Attach to a rule
   116  		env.Rule.Call.Comments.Before = comment
   117  	case 2: // Attach to an attribute
   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: // Attach to a specific value in a list
   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  // commentsText concatenates comments into a single line.
   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: // Print rule comment.
   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: // Print attribute comment.
   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: // Print comment of a specific value in a list.
   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  // findInsertionIndex is used by cmdNew to find the place at which to insert the new rule.
   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  // splitLoadArgs splits arguments of form <[to=]from>
   285  // into a slice of froms and a slice of tos.
   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  			// Some other Expr we haven't listed above. Just print it.
   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 { // Remove the attribute
   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 { // Remove values in the attribute.
   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  			// Remove the attribute if's an empty list
   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: // Remove comment attached to rule
   481  		env.Rule.Call.Comments.Before = nil
   482  		env.Rule.Call.Comments.Suffix = nil
   483  		env.Rule.Call.Comments.After = nil
   484  	case 1: // Remove comment attached to attr
   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: // Remove comment attached to value
   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  		// Expected a non-list argument, nothing provided
   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  // getStringValue extracts a string value, which can be either quoted or not, from an input argument
   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  // getStringExpr creates a StringExpr from an input argument, which can be either quoted or not,
   613  // and shortens the label value if possible.
   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  // cmdDictAdd adds a key to a dict, if that key does _not_ exit already.
   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  			// Only set the value if the value is currently unset.
   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  		// Set overwrites previous values.
   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  // cmdDictSet adds a key to a dict, overwriting any previous values.
   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  		// Set overwrites previous values.
   717  		DictionarySet(dict, kv[0], expr)
   718  	}
   719  	env.Rule.SetAttr(attr, dict)
   720  	return env.File, nil
   721  }
   722  
   723  // cmdDictRemove removes a key from a dict.
   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  		// should errors here be flagged?
   736  		DictionaryDelete(dictAttr, x)
   737  		env.Rule.SetAttr(attr, dictAttr)
   738  	}
   739  
   740  	// If the removal results in the dict having no contents, delete the attribute (stay clean!)
   741  	if dictAttr == nil || len(dictAttr.List) == 0 {
   742  		env.Rule.DelAttr(attr)
   743  	}
   744  
   745  	return env.File, nil
   746  }
   747  
   748  // cmdDictListAdd adds an item to a list in a dict.
   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("" /* filename */, []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  	// Force formatting by not returning a nil *build.File.
   853  	return env.File, nil
   854  }
   855  
   856  func isExtensionLabel(arg string) bool {
   857  	// Labels referencing extensions are either absolute or repo-absolute. Repository names are not
   858  	// allowed to contain "@" or "/".
   859  	return strings.HasPrefix(arg, "@") || strings.HasSuffix(arg, "//")
   860  }
   861  
   862  func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
   863  	// Fix the whole file
   864  	if env.Rule.Kind() == "package" {
   865  		return FixFile(env.File, env.Pkg, env.Args), nil
   866  	}
   867  	// Fix a specific rule
   868  	return FixRule(env.File, env.Pkg, env.Rule, env.Args), nil
   869  }
   870  
   871  // CommandInfo provides a command function and info on incoming arguments.
   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  // AllCommands associates the command names with their function and number
   881  // of arguments.
   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  		// "all" is a valid name, it is a wildcard only if no such rule is found.
   926  		return f.Rules(""), nil
   927  	} else if strings.HasPrefix(rule, "%") {
   928  		// "%java_library" will match all java_library functions in the package
   929  		// "%<LINENUM>" will match the rule which begins at LINENUM.
   930  		// This is for convenience, "%" is not a valid character in bazel targets.
   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  // command contains a list of tokens that describe a buildozer command.
   959  type command struct {
   960  	tokens []string
   961  }
   962  
   963  // checkCommandUsage checks the number of argument of a command.
   964  // It prints an error and usage when it is not valid.
   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  // Match text that only contains spaces or line breaks if they're escaped with '\'.
   982  var spaceRegex = regexp.MustCompile(`(\\ |\\\n|[^ \n])+`)
   983  
   984  // SplitOnSpaces behaves like strings.Fields, except that spaces can be escaped.
   985  // Also splits on linebreaks unless they are escaped too.
   986  // " some dummy\\ string" -> ["some", "dummy string"]
   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  // parseCommands parses commands and targets they should be applied on from
   998  // a list of arguments.
   999  // Each argument can be either:
  1000  //   - a command (as defined by AllCommands) and its parameters, separated by
  1001  //     whitespace
  1002  //   - a target all commands that are parsed during one call to parseCommands
  1003  //     should be applied on
  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  // commandsForTarget contains commands to be executed on the given target.
  1023  type commandsForTarget struct {
  1024  	target   string
  1025  	commands []command
  1026  }
  1027  
  1028  // commandsForFile contains the file name and all commands that should be
  1029  // applied on that file, indexed by their target.
  1030  type commandsForFile struct {
  1031  	file     string
  1032  	commands []commandsForTarget
  1033  }
  1034  
  1035  // commandError returns an error that formats 'err' in the context of the
  1036  // commands to be executed on the given target.
  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  // rewriteResult contains the outcome of applying fixes to a single file.
  1042  type rewriteResult struct {
  1043  	file     string
  1044  	errs     []error
  1045  	modified bool
  1046  	records  []*apipb.Output_Record
  1047  }
  1048  
  1049  // getGlobalVariables returns the global variable assignments in the provided list of expressions.
  1050  // That is, for each variable assignment of the form
  1051  //
  1052  //	a = v
  1053  //
  1054  // vars["a"] will contain the AssignExpr whose RHS value is the assignment "a = v".
  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  // When checking the filesystem, we need to look for any of the
  1068  // possible BuildFileNames. For historical reasons, the
  1069  // parts of the tool that generate paths that we may want to examine
  1070  // continue to assume that build files are all named "BUILD".
  1071  
  1072  // BuildFileNames is exported so that users that want to override it
  1073  // in scripts are free to do so.
  1074  var BuildFileNames = [...]string{"BUILD.bazel", "BUILD", "BUCK"}
  1075  
  1076  // Buildifier formats the build file using the buildifier logic.
  1077  type Buildifier interface {
  1078  	// Buildify formats the build file and returns the formatted contents.
  1079  	Buildify(*Options, *build.File) ([]byte, error)
  1080  }
  1081  
  1082  var (
  1083  	buildifier           Buildifier = &defaultBuildifier{}
  1084  	buildifierRegistered            = false
  1085  )
  1086  
  1087  // RegisterBuildifier replaces the default buildifier with an
  1088  // alternative implementation.
  1089  //
  1090  // It may only be called once.
  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  // rewrite parses the BUILD file for the given file, transforms the AST,
  1100  // and write the changes back in the file (or on stdout).
  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 { // read on stdin
  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  		// Buildozer is unable to infer the file type, fall back to BUILD by default.
  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  			// Special-case: This is already absolute
  1159  			absPkg = stdinPackageName
  1160  		}
  1161  		if strings.HasSuffix(absPkg, "...") {
  1162  			// Special case: the provided target contains an ellipsis, use the file package
  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  			// Depending on whether a transformation is rule-specific or not, it should be applied to
  1178  			// every rule that satisfies the filter or just once to the file.
  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  // EditFile is a function that does any prework needed before editing a file.
  1235  // e.g. "checking out for write" from a locking source control repo.
  1236  var EditFile = func(fi os.FileInfo, name string) error {
  1237  	return nil
  1238  }
  1239  
  1240  // Given a target, whose package may contain a trailing "/...", returns all
  1241  // existing BUILD file paths which match the package.
  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") // /.../BUILD
  1253  	if !strings.HasSuffix(file, suffix) {
  1254  		return []string{file}
  1255  	}
  1256  
  1257  	return findBuildFiles(strings.TrimSuffix(file, suffix))
  1258  }
  1259  
  1260  // Given a root directory, returns all "BUILD" files in that subtree recursively.
  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  // appendCommands adds the given commands to be applied to each of the given targets
  1292  // via the commandMap.
  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  // Buildozer loops over all arguments on the command line fixing BUILD files.
  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  	// The file is not modified, check if there were any non-readonly commands
  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