github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/merger/fix.go (about)

     1  /* Copyright 2017 The Bazel Authors. All rights reserved.
     2  
     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  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package merger
    17  
    18  import (
    19  	"log"
    20  	"sort"
    21  
    22  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    23  	bf "github.com/bazelbuild/buildtools/build"
    24  )
    25  
    26  // Much of this file could be simplified by using
    27  // github.com/bazelbuild/buildtools/edit. However, through a transitive
    28  // dependency, that library depends on a proto in Bazel itself, which is
    29  // a 95MB download. Not worth it.
    30  
    31  // FixFile updates rules in oldFile that were generated by an older version of
    32  // Gazelle to a newer form that can be merged with freshly generated rules.
    33  //
    34  // FixLoads should be called after this, since it will fix load
    35  // statements that may be broken by transformations applied by this function.
    36  func FixFile(c *config.Config, oldFile *bf.File) *bf.File {
    37  	fixedFile := squashCgoLibrary(oldFile)
    38  	return removeLegacyProto(c, fixedFile)
    39  }
    40  
    41  // squashCgoLibrary removes cgo_library rules with the default name and
    42  // merges their attributes with go_library with the default name. If no
    43  // go_library rule exists, a new one will be created.
    44  //
    45  // Note that the library attribute is disregarded, so cgo_library and
    46  // go_library attributes will be squashed even if the cgo_library was unlinked.
    47  // MergeWithExisting will remove unused values and attributes later.
    48  func squashCgoLibrary(oldFile *bf.File) *bf.File {
    49  	// Find the default cgo_library and go_library rules.
    50  	var cgoLibrary, goLibrary bf.Rule
    51  	cgoLibraryIndex := -1
    52  	goLibraryIndex := -1
    53  
    54  	for i, stmt := range oldFile.Stmt {
    55  		c, ok := stmt.(*bf.CallExpr)
    56  		if !ok {
    57  			continue
    58  		}
    59  		r := bf.Rule{Call: c}
    60  		if r.Kind() == "cgo_library" && r.Name() == config.DefaultCgoLibName && !shouldKeep(c) {
    61  			if cgoLibrary.Call != nil {
    62  				log.Printf("%s: when fixing existing file, multiple cgo_library rules with default name found", oldFile.Path)
    63  				continue
    64  			}
    65  			cgoLibrary = r
    66  			cgoLibraryIndex = i
    67  			continue
    68  		}
    69  		if r.Kind() == "go_library" && r.Name() == config.DefaultLibName {
    70  			if goLibrary.Call != nil {
    71  				log.Printf("%s: when fixing existing file, multiple go_library rules with default name referencing cgo_library found", oldFile.Path)
    72  				continue
    73  			}
    74  			goLibrary = r
    75  			goLibraryIndex = i
    76  		}
    77  	}
    78  
    79  	if cgoLibrary.Call == nil {
    80  		return oldFile
    81  	}
    82  
    83  	// If go_library has a '# keep' comment, just delete cgo_library.
    84  	if goLibrary.Call != nil && shouldKeep(goLibrary.Call) {
    85  		fixedFile := *oldFile
    86  		fixedFile.Stmt = append(fixedFile.Stmt[:cgoLibraryIndex], fixedFile.Stmt[cgoLibraryIndex+1:]...)
    87  		return &fixedFile
    88  	}
    89  
    90  	// Copy the comments and attributes from cgo_library into go_library. If no
    91  	// go_library exists, create an empty one.
    92  	var fixedGoLibraryExpr bf.CallExpr
    93  	fixedGoLibrary := bf.Rule{Call: &fixedGoLibraryExpr}
    94  	if goLibrary.Call == nil {
    95  		fixedGoLibrary.SetKind("go_library")
    96  		fixedGoLibrary.SetAttr("name", &bf.StringExpr{Value: config.DefaultLibName})
    97  		if vis := cgoLibrary.Attr("visibility"); vis != nil {
    98  			fixedGoLibrary.SetAttr("visibility", vis)
    99  		}
   100  	} else {
   101  		fixedGoLibraryExpr = *goLibrary.Call
   102  		fixedGoLibraryExpr.List = append([]bf.Expr{}, goLibrary.Call.List...)
   103  	}
   104  
   105  	fixedGoLibrary.DelAttr("embed")
   106  	fixedGoLibrary.SetAttr("cgo", &bf.LiteralExpr{Token: "True"})
   107  
   108  	fixedGoLibraryExpr.Comments.Before = append(fixedGoLibraryExpr.Comments.Before, cgoLibrary.Call.Comments.Before...)
   109  	fixedGoLibraryExpr.Comments.Suffix = append(fixedGoLibraryExpr.Comments.Suffix, cgoLibrary.Call.Comments.Suffix...)
   110  	fixedGoLibraryExpr.Comments.After = append(fixedGoLibraryExpr.Comments.After, cgoLibrary.Call.Comments.After...)
   111  
   112  	for _, key := range []string{"cdeps", "clinkopts", "copts", "data", "deps", "gc_goopts", "srcs"} {
   113  		goLibraryAttr := fixedGoLibrary.Attr(key)
   114  		cgoLibraryAttr := cgoLibrary.Attr(key)
   115  		if cgoLibraryAttr == nil {
   116  			continue
   117  		}
   118  		if fixedAttr, err := squashExpr(goLibraryAttr, cgoLibraryAttr); err == nil {
   119  			fixedGoLibrary.SetAttr(key, fixedAttr)
   120  		}
   121  	}
   122  
   123  	// Rebuild the file with the cgo_library removed and the go_library replaced.
   124  	// If the go_library didn't already exist, it will replace cgo_library.
   125  	fixedFile := *oldFile
   126  	if goLibrary.Call == nil {
   127  		fixedFile.Stmt = append([]bf.Expr{}, oldFile.Stmt...)
   128  		fixedFile.Stmt[cgoLibraryIndex] = &fixedGoLibraryExpr
   129  	} else {
   130  		fixedFile.Stmt = append(oldFile.Stmt[:cgoLibraryIndex], oldFile.Stmt[cgoLibraryIndex+1:]...)
   131  		if goLibraryIndex > cgoLibraryIndex {
   132  			goLibraryIndex--
   133  		}
   134  		fixedFile.Stmt[goLibraryIndex] = &fixedGoLibraryExpr
   135  	}
   136  	return &fixedFile
   137  }
   138  
   139  // squashExpr combines two expressions. Unlike mergeExpr, squashExpr does not
   140  // discard information from an "old" expression. It does not sort or de-duplicate
   141  // elements. Any non-scalar expressions that mergeExpr understands can be
   142  // squashed.
   143  func squashExpr(x, y bf.Expr) (bf.Expr, error) {
   144  	xExprs, err := extractPlatformStringsExprs(x)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	yExprs, err := extractPlatformStringsExprs(y)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	squashedExprs, err := squashPlatformStringsExprs(xExprs, yExprs)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	return makePlatformStringsExpr(squashedExprs), nil
   157  }
   158  
   159  func squashPlatformStringsExprs(x, y platformStringsExprs) (platformStringsExprs, error) {
   160  	var ps platformStringsExprs
   161  	var err error
   162  	ps.generic = squashList(x.generic, y.generic)
   163  	if ps.os, err = squashDict(x.os, y.os); err != nil {
   164  		return platformStringsExprs{}, err
   165  	}
   166  	if ps.arch, err = squashDict(x.arch, y.arch); err != nil {
   167  		return platformStringsExprs{}, err
   168  	}
   169  	if ps.platform, err = squashDict(x.platform, y.platform); err != nil {
   170  		return platformStringsExprs{}, err
   171  	}
   172  	return ps, nil
   173  }
   174  
   175  func squashList(x, y *bf.ListExpr) *bf.ListExpr {
   176  	if x == nil {
   177  		return y
   178  	}
   179  	if y == nil {
   180  		return x
   181  	}
   182  	squashed := *x
   183  	squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...)
   184  	squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...)
   185  	squashed.Comments.After = append(x.Comments.After, y.Comments.After...)
   186  	squashed.List = append(x.List, y.List...)
   187  	return &squashed
   188  }
   189  
   190  func squashDict(x, y *bf.DictExpr) (*bf.DictExpr, error) {
   191  	if x == nil {
   192  		return y, nil
   193  	}
   194  	if y == nil {
   195  		return x, nil
   196  	}
   197  
   198  	squashed := *x
   199  	squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...)
   200  	squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...)
   201  	squashed.Comments.After = append(x.Comments.After, y.Comments.After...)
   202  
   203  	xCaseIndex := make(map[string]int)
   204  	for i, e := range x.List {
   205  		kv, ok := e.(*bf.KeyValueExpr)
   206  		if !ok {
   207  			continue
   208  		}
   209  		key, ok := kv.Key.(*bf.StringExpr)
   210  		if !ok {
   211  			continue
   212  		}
   213  		xCaseIndex[key.Value] = i
   214  	}
   215  
   216  	for _, e := range y.List {
   217  		kv, ok := e.(*bf.KeyValueExpr)
   218  		if !ok {
   219  			squashed.List = append(squashed.List, e)
   220  			continue
   221  		}
   222  		key, ok := e.(*bf.StringExpr)
   223  		if !ok {
   224  			squashed.List = append(squashed.List, e)
   225  			continue
   226  		}
   227  		i, ok := xCaseIndex[key.Value]
   228  		if !ok {
   229  			squashed.List = append(squashed.List, e)
   230  			continue
   231  		}
   232  		squashedElem, err := squashExpr(x.List[i], kv.Value)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		x.List[i] = squashedElem
   237  	}
   238  
   239  	return &squashed, nil
   240  }
   241  
   242  // removeLegacyProto removes uses of the old proto rules. It deletes loads
   243  // from go_proto_library.bzl. It deletes proto filegroups. It removes
   244  // go_proto_library attributes which are no longer recognized. New rules
   245  // are generated in place of the deleted rules, but attributes and comments
   246  // are not migrated.
   247  func removeLegacyProto(c *config.Config, oldFile *bf.File) *bf.File {
   248  	// Don't fix if the proto mode was set to something other than the default.
   249  	if c.ProtoMode != config.DefaultProtoMode {
   250  		return oldFile
   251  	}
   252  
   253  	// Scan for definitions to delete.
   254  	var deletedIndices []int
   255  	var protoIndices []int
   256  	shouldDeleteProtos := false
   257  	for i, stmt := range oldFile.Stmt {
   258  		c, ok := stmt.(*bf.CallExpr)
   259  		if !ok {
   260  			continue
   261  		}
   262  		x, ok := c.X.(*bf.LiteralExpr)
   263  		if !ok {
   264  			continue
   265  		}
   266  
   267  		if x.Token == "load" && len(c.List) > 0 {
   268  			if name, ok := c.List[0].(*bf.StringExpr); ok && name.Value == "@io_bazel_rules_go//proto:go_proto_library.bzl" {
   269  				deletedIndices = append(deletedIndices, i)
   270  				shouldDeleteProtos = true
   271  			}
   272  			continue
   273  		}
   274  		if x.Token == "filegroup" {
   275  			r := bf.Rule{Call: c}
   276  			if r.Name() == config.DefaultProtosName {
   277  				deletedIndices = append(deletedIndices, i)
   278  			}
   279  			continue
   280  		}
   281  		if x.Token == "go_proto_library" {
   282  			protoIndices = append(protoIndices, i)
   283  		}
   284  	}
   285  	if len(deletedIndices) == 0 {
   286  		return oldFile
   287  	}
   288  
   289  	// Rebuild the file without deleted statements. Only delete go_proto_library
   290  	// rules if we deleted a load.
   291  	if shouldDeleteProtos {
   292  		deletedIndices = append(deletedIndices, protoIndices...)
   293  		sort.Ints(deletedIndices)
   294  	}
   295  	fixedFile := *oldFile
   296  	fixedFile.Stmt = deleteIndices(oldFile.Stmt, deletedIndices)
   297  	return &fixedFile
   298  }
   299  
   300  // FixFileMinor updates rules in oldFile that were generated by an older version
   301  // of Gazelle to a newer form that can be merged with freshly generated rules.
   302  //
   303  // FixFileMinor includes only small, low-risk fixes that can be applied in
   304  // update mode. When both FixFileMinor and FixFile are called, FixFileMinor
   305  // should be called first.
   306  //
   307  // FixLoads should be called after this, since it will fix load
   308  // statements that may be broken by transformations applied by this function.
   309  func FixFileMinor(c *config.Config, oldFile *bf.File) *bf.File {
   310  	fixedFile := migrateLibraryEmbed(c, oldFile)
   311  	fixedFile = migrateGrpcCompilers(c, fixedFile)
   312  	return removeBinaryImportPath(c, fixedFile)
   313  }
   314  
   315  // migrateLibraryEmbed converts "library" attributes to "embed" attributes,
   316  // preserving comments. This only applies to Go rules, and only if there is
   317  // no keep comment on "library" and no existing "embed" attribute.
   318  func migrateLibraryEmbed(c *config.Config, oldFile *bf.File) *bf.File {
   319  	fixed := false
   320  	fixedFile := *oldFile
   321  	for i, stmt := range fixedFile.Stmt {
   322  		call, ok := stmt.(*bf.CallExpr)
   323  		if !ok {
   324  			continue
   325  		}
   326  		rule := bf.Rule{Call: call}
   327  		if kind := rule.Kind(); !isGoRule(kind) || shouldKeep(stmt) {
   328  			continue
   329  		}
   330  		libExpr := rule.Attr("library")
   331  		if libExpr == nil || shouldKeep(libExpr) || rule.Attr("embed") != nil {
   332  			continue
   333  		}
   334  
   335  		fixedCall := *call
   336  		rule.Call = &fixedCall
   337  		rule.DelAttr("library")
   338  		rule.SetAttr("embed", &bf.ListExpr{List: []bf.Expr{libExpr}})
   339  		fixedFile.Stmt[i] = &fixedCall
   340  		fixed = true
   341  	}
   342  	if !fixed {
   343  		return oldFile
   344  	}
   345  	return &fixedFile
   346  }
   347  
   348  // migrateGrpcCompilers converts "go_grpc_library" rules into "go_proto_library"
   349  // rules with a "compilers" attribute.
   350  func migrateGrpcCompilers(c *config.Config, oldFile *bf.File) *bf.File {
   351  	fixed := false
   352  	fixedFile := *oldFile
   353  	for i, stmt := range fixedFile.Stmt {
   354  		call, ok := stmt.(*bf.CallExpr)
   355  		if !ok {
   356  			continue
   357  		}
   358  		rule := bf.Rule{Call: call}
   359  		if rule.Kind() != "go_grpc_library" || shouldKeep(stmt) || rule.Attr("compilers") != nil {
   360  			continue
   361  		}
   362  
   363  		fixedCall := *call
   364  		fixedCall.List = make([]bf.Expr, len(call.List))
   365  		copy(fixedCall.List, call.List)
   366  		rule.Call = &fixedCall
   367  		rule.SetKind("go_proto_library")
   368  		rule.SetAttr("compilers", &bf.ListExpr{
   369  			List: []bf.Expr{&bf.StringExpr{Value: config.GrpcCompilerLabel}},
   370  		})
   371  		fixedFile.Stmt[i] = &fixedCall
   372  		fixed = true
   373  	}
   374  	if !fixed {
   375  		return oldFile
   376  	}
   377  	return &fixedFile
   378  }
   379  
   380  // removeBinaryImportPath removes "importpath" attributes from "go_binary"
   381  // and "go_test" rules. These are now deprecated.
   382  func removeBinaryImportPath(c *config.Config, oldFile *bf.File) *bf.File {
   383  	fixed := false
   384  	fixedFile := *oldFile
   385  	for i, stmt := range fixedFile.Stmt {
   386  		call, ok := stmt.(*bf.CallExpr)
   387  		if !ok {
   388  			continue
   389  		}
   390  		rule := bf.Rule{Call: call}
   391  		if rule.Kind() != "go_binary" && rule.Kind() != "go_test" || rule.Attr("importpath") == nil {
   392  			continue
   393  		}
   394  
   395  		fixedCall := *call
   396  		fixedCall.List = make([]bf.Expr, len(call.List))
   397  		copy(fixedCall.List, call.List)
   398  		rule.Call = &fixedCall
   399  		rule.DelAttr("importpath")
   400  		fixedFile.Stmt[i] = &fixedCall
   401  		fixed = true
   402  	}
   403  	if !fixed {
   404  		return oldFile
   405  	}
   406  	return &fixedFile
   407  }
   408  
   409  // FixLoads removes loads of unused go rules and adds loads of newly used rules.
   410  // This should be called after FixFile and MergeWithExisting, since symbols
   411  // may be introduced that aren't loaded.
   412  func FixLoads(oldFile *bf.File) *bf.File {
   413  	// Make a list of load statements in the file. Keep track of loads of known
   414  	// files, since these may be changed. Keep track of known symbols loaded from
   415  	// unknown files; we will not add loads for these.
   416  	type loadInfo struct {
   417  		index      int
   418  		file       string
   419  		old, fixed *bf.CallExpr
   420  	}
   421  	var loads []loadInfo
   422  	otherLoadedKinds := make(map[string]bool)
   423  	for i, stmt := range oldFile.Stmt {
   424  		c, ok := stmt.(*bf.CallExpr)
   425  		if !ok {
   426  			continue
   427  		}
   428  		x, ok := c.X.(*bf.LiteralExpr)
   429  		if !ok || x.Token != "load" {
   430  			continue
   431  		}
   432  
   433  		if len(c.List) == 0 {
   434  			continue
   435  		}
   436  		label, ok := c.List[0].(*bf.StringExpr)
   437  		if !ok {
   438  			continue
   439  		}
   440  
   441  		if knownFiles[label.Value] {
   442  			loads = append(loads, loadInfo{index: i, file: label.Value, old: c})
   443  			continue
   444  		}
   445  		for _, arg := range c.List[1:] {
   446  			switch sym := arg.(type) {
   447  			case *bf.StringExpr:
   448  				otherLoadedKinds[sym.Value] = true
   449  			case *bf.BinaryExpr:
   450  				if sym.Op != "=" {
   451  					continue
   452  				}
   453  				if x, ok := sym.X.(*bf.LiteralExpr); ok {
   454  					otherLoadedKinds[x.Token] = true
   455  				}
   456  			}
   457  		}
   458  	}
   459  
   460  	// Make a map of all the symbols from known files used in this file.
   461  	usedKinds := make(map[string]map[string]bool)
   462  	for _, stmt := range oldFile.Stmt {
   463  		c, ok := stmt.(*bf.CallExpr)
   464  		if !ok {
   465  			continue
   466  		}
   467  		x, ok := c.X.(*bf.LiteralExpr)
   468  		if !ok {
   469  			continue
   470  		}
   471  
   472  		kind := x.Token
   473  		if file, ok := knownKinds[kind]; ok && !otherLoadedKinds[kind] {
   474  			if usedKinds[file] == nil {
   475  				usedKinds[file] = make(map[string]bool)
   476  			}
   477  			usedKinds[file][kind] = true
   478  		}
   479  	}
   480  
   481  	// Fix the load statements. The order is important, so we iterate over
   482  	// knownLoads instead of knownFiles.
   483  	changed := false
   484  	var newFirstLoads []*bf.CallExpr
   485  	for _, l := range knownLoads {
   486  		file := l.file
   487  		first := true
   488  		for i, _ := range loads {
   489  			li := &loads[i]
   490  			if li.file != file {
   491  				continue
   492  			}
   493  			if first {
   494  				li.fixed = fixLoad(li.old, file, usedKinds[file])
   495  				first = false
   496  			} else {
   497  				li.fixed = fixLoad(li.old, file, nil)
   498  			}
   499  			changed = changed || li.fixed != li.old
   500  		}
   501  		if first {
   502  			load := fixLoad(nil, file, usedKinds[file])
   503  			if load != nil {
   504  				newFirstLoads = append(newFirstLoads, load)
   505  				changed = true
   506  			}
   507  		}
   508  	}
   509  	if !changed {
   510  		return oldFile
   511  	}
   512  
   513  	// Rebuild the file.
   514  	fixedFile := *oldFile
   515  	fixedFile.Stmt = make([]bf.Expr, 0, len(oldFile.Stmt)+len(newFirstLoads))
   516  	for _, l := range newFirstLoads {
   517  		fixedFile.Stmt = append(fixedFile.Stmt, l)
   518  	}
   519  	loadIndex := 0
   520  	for i, stmt := range oldFile.Stmt {
   521  		if loadIndex < len(loads) && i == loads[loadIndex].index {
   522  			if loads[loadIndex].fixed != nil {
   523  				fixedFile.Stmt = append(fixedFile.Stmt, loads[loadIndex].fixed)
   524  			}
   525  			loadIndex++
   526  			continue
   527  		}
   528  		fixedFile.Stmt = append(fixedFile.Stmt, stmt)
   529  	}
   530  	return &fixedFile
   531  }
   532  
   533  // knownLoads is a list of files Gazelle will generate loads from and
   534  // the symbols it knows about.  All symbols Gazelle ever generated
   535  // loads for are present, including symbols it no longer uses (e.g.,
   536  // cgo_library). Manually loaded symbols (e.g., go_embed_data) are not
   537  // included. The order of the files here will match the order of
   538  // generated load statements. The symbols should be sorted
   539  // lexicographically.
   540  var knownLoads = []struct {
   541  	file  string
   542  	kinds []string
   543  }{
   544  	{
   545  		"@io_bazel_rules_go//go:def.bzl",
   546  		[]string{
   547  			"cgo_library",
   548  			"go_binary",
   549  			"go_library",
   550  			"go_prefix",
   551  			"go_repository",
   552  			"go_test",
   553  		},
   554  	}, {
   555  		"@io_bazel_rules_go//proto:def.bzl",
   556  		[]string{
   557  			"go_grpc_library",
   558  			"go_proto_library",
   559  		},
   560  	},
   561  }
   562  
   563  // knownFiles is the set of labels for files that Gazelle loads symbols from.
   564  var knownFiles map[string]bool
   565  
   566  // knownKinds is a map from symbols to labels of the files they are loaded
   567  // from.
   568  var knownKinds map[string]string
   569  
   570  func init() {
   571  	knownFiles = make(map[string]bool)
   572  	knownKinds = make(map[string]string)
   573  	for _, l := range knownLoads {
   574  		knownFiles[l.file] = true
   575  		for _, k := range l.kinds {
   576  			knownKinds[k] = l.file
   577  		}
   578  	}
   579  }
   580  
   581  // fixLoad updates a load statement. load must be a load statement for
   582  // the Go rules or nil. If nil, a new statement may be created. Symbols in
   583  // kinds are added if they are not already present, symbols in knownKinds
   584  // are removed if they are not in kinds, and other symbols and arguments
   585  // are preserved. nil is returned if the statement should be deleted because
   586  // it is empty.
   587  func fixLoad(load *bf.CallExpr, file string, kinds map[string]bool) *bf.CallExpr {
   588  	var fixed bf.CallExpr
   589  	if load == nil {
   590  		fixed = bf.CallExpr{
   591  			X: &bf.LiteralExpr{Token: "load"},
   592  			List: []bf.Expr{
   593  				&bf.StringExpr{Value: file},
   594  			},
   595  			ForceCompact: true,
   596  		}
   597  	} else {
   598  		fixed = *load
   599  	}
   600  
   601  	var symbols []*bf.StringExpr
   602  	var otherArgs []bf.Expr
   603  	loadedKinds := make(map[string]bool)
   604  	var added, removed int
   605  	for _, arg := range fixed.List[1:] {
   606  		if s, ok := arg.(*bf.StringExpr); ok {
   607  			if knownKinds[s.Value] == "" || kinds != nil && kinds[s.Value] {
   608  				symbols = append(symbols, s)
   609  				loadedKinds[s.Value] = true
   610  			} else {
   611  				removed++
   612  			}
   613  		} else {
   614  			otherArgs = append(otherArgs, arg)
   615  		}
   616  	}
   617  	if kinds != nil {
   618  		for kind, _ := range kinds {
   619  			if _, ok := loadedKinds[kind]; !ok {
   620  				symbols = append(symbols, &bf.StringExpr{Value: kind})
   621  				added++
   622  			}
   623  		}
   624  	}
   625  	if added == 0 && removed == 0 {
   626  		if load != nil && len(load.List) == 1 {
   627  			// Special case: delete existing empty load.
   628  			return nil
   629  		}
   630  		return load
   631  	}
   632  
   633  	sort.Stable(byString(symbols))
   634  	fixed.List = fixed.List[:1]
   635  	for _, sym := range symbols {
   636  		fixed.List = append(fixed.List, sym)
   637  	}
   638  	fixed.List = append(fixed.List, otherArgs...)
   639  	if len(fixed.List) == 1 {
   640  		return nil
   641  	}
   642  	return &fixed
   643  }
   644  
   645  type byString []*bf.StringExpr
   646  
   647  func (s byString) Len() int {
   648  	return len(s)
   649  }
   650  
   651  func (s byString) Less(i, j int) bool {
   652  	return s[i].Value < s[j].Value
   653  }
   654  
   655  func (s byString) Swap(i, j int) {
   656  	s[i], s[j] = s[j], s[i]
   657  }
   658  
   659  func isGoRule(kind string) bool {
   660  	return kind == "go_library" ||
   661  		kind == "go_binary" ||
   662  		kind == "go_test" ||
   663  		kind == "go_proto_library" ||
   664  		kind == "go_grpc_library"
   665  }