github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/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 golang
    17  
    18  import (
    19  	"log"
    20  
    21  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    22  	"github.com/bazelbuild/bazel-gazelle/internal/language/proto"
    23  	"github.com/bazelbuild/bazel-gazelle/internal/rule"
    24  	bzl "github.com/bazelbuild/buildtools/build"
    25  )
    26  
    27  func (_ *goLang) Fix(c *config.Config, f *rule.File) {
    28  	migrateLibraryEmbed(c, f)
    29  	migrateGrpcCompilers(c, f)
    30  	flattenSrcs(c, f)
    31  	squashCgoLibrary(c, f)
    32  	squashXtest(c, f)
    33  	removeLegacyProto(c, f)
    34  	removeLegacyGazelle(c, f)
    35  }
    36  
    37  // migrateLibraryEmbed converts "library" attributes to "embed" attributes,
    38  // preserving comments. This only applies to Go rules, and only if there is
    39  // no keep comment on "library" and no existing "embed" attribute.
    40  func migrateLibraryEmbed(c *config.Config, f *rule.File) {
    41  	for _, r := range f.Rules {
    42  		if !isGoRule(r.Kind()) {
    43  			continue
    44  		}
    45  		libExpr := r.Attr("library")
    46  		if libExpr == nil || rule.ShouldKeep(libExpr) || r.Attr("embed") != nil {
    47  			continue
    48  		}
    49  		r.DelAttr("library")
    50  		r.SetAttr("embed", &bzl.ListExpr{List: []bzl.Expr{libExpr}})
    51  	}
    52  }
    53  
    54  // migrateGrpcCompilers converts "go_grpc_library" rules into "go_proto_library"
    55  // rules with a "compilers" attribute.
    56  func migrateGrpcCompilers(c *config.Config, f *rule.File) {
    57  	for _, r := range f.Rules {
    58  		if r.Kind() != "go_grpc_library" || r.ShouldKeep() || r.Attr("compilers") != nil {
    59  			continue
    60  		}
    61  		r.SetKind("go_proto_library")
    62  		r.SetAttr("compilers", []string{config.GrpcCompilerLabel})
    63  	}
    64  }
    65  
    66  // squashCgoLibrary removes cgo_library rules with the default name and
    67  // merges their attributes with go_library with the default name. If no
    68  // go_library rule exists, a new one will be created.
    69  //
    70  // Note that the library attribute is disregarded, so cgo_library and
    71  // go_library attributes will be squashed even if the cgo_library was unlinked.
    72  // MergeFile will remove unused values and attributes later.
    73  func squashCgoLibrary(c *config.Config, f *rule.File) {
    74  	// Find the default cgo_library and go_library rules.
    75  	var cgoLibrary, goLibrary *rule.Rule
    76  	for _, r := range f.Rules {
    77  		if r.Kind() == "cgo_library" && r.Name() == config.DefaultCgoLibName && !r.ShouldKeep() {
    78  			if cgoLibrary != nil {
    79  				log.Printf("%s: when fixing existing file, multiple cgo_library rules with default name found", f.Path)
    80  				continue
    81  			}
    82  			cgoLibrary = r
    83  			continue
    84  		}
    85  		if r.Kind() == "go_library" && r.Name() == config.DefaultLibName {
    86  			if goLibrary != nil {
    87  				log.Printf("%s: when fixing existing file, multiple go_library rules with default name referencing cgo_library found", f.Path)
    88  			}
    89  			goLibrary = r
    90  			continue
    91  		}
    92  	}
    93  
    94  	if cgoLibrary == nil {
    95  		return
    96  	}
    97  	if !c.ShouldFix {
    98  		log.Printf("%s: cgo_library is deprecated. Run 'gazelle fix' to squash with go_library.", f.Path)
    99  		return
   100  	}
   101  
   102  	if goLibrary == nil {
   103  		cgoLibrary.SetKind("go_library")
   104  		cgoLibrary.SetName(config.DefaultLibName)
   105  		cgoLibrary.SetAttr("cgo", true)
   106  		return
   107  	}
   108  
   109  	if err := rule.SquashRules(cgoLibrary, goLibrary, f.Path); err != nil {
   110  		log.Print(err)
   111  		return
   112  	}
   113  	goLibrary.DelAttr("embed")
   114  	goLibrary.SetAttr("cgo", true)
   115  	cgoLibrary.Delete()
   116  }
   117  
   118  // squashXtest removes go_test rules with the default external name and merges
   119  // their attributes with a go_test rule with the default internal name. If
   120  // no internal go_test rule exists, a new one will be created (effectively
   121  // renaming the old rule).
   122  func squashXtest(c *config.Config, f *rule.File) {
   123  	// Search for internal and external tests.
   124  	var itest, xtest *rule.Rule
   125  	for _, r := range f.Rules {
   126  		if r.Kind() != "go_test" {
   127  			continue
   128  		}
   129  		if r.Name() == config.DefaultTestName {
   130  			itest = r
   131  		} else if r.Name() == config.DefaultXTestName {
   132  			xtest = r
   133  		}
   134  	}
   135  
   136  	if xtest == nil || xtest.ShouldKeep() || (itest != nil && itest.ShouldKeep()) {
   137  		return
   138  	}
   139  	if !c.ShouldFix {
   140  		if itest == nil {
   141  			log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to rename to go_default_test.", f.Path)
   142  		} else {
   143  			log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to squash with go_default_test.", f.Path)
   144  		}
   145  		return
   146  	}
   147  
   148  	// If there was no internal test, we can just rename the external test.
   149  	if itest == nil {
   150  		xtest.SetName(config.DefaultTestName)
   151  		return
   152  	}
   153  
   154  	// Attempt to squash.
   155  	if err := rule.SquashRules(xtest, itest, f.Path); err != nil {
   156  		log.Print(err)
   157  		return
   158  	}
   159  	xtest.Delete()
   160  }
   161  
   162  // flattenSrcs transforms srcs attributes structured as concatenations of
   163  // lists and selects (generated from PlatformStrings; see
   164  // extractPlatformStringsExprs for matching details) into a sorted,
   165  // de-duplicated list. Comments are accumulated and de-duplicated across
   166  // duplicate expressions.
   167  func flattenSrcs(c *config.Config, f *rule.File) {
   168  	for _, r := range f.Rules {
   169  		if !isGoRule(r.Kind()) {
   170  			continue
   171  		}
   172  		oldSrcs := r.Attr("srcs")
   173  		if oldSrcs == nil {
   174  			continue
   175  		}
   176  		flatSrcs := rule.FlattenExpr(oldSrcs)
   177  		if flatSrcs != oldSrcs {
   178  			r.SetAttr("srcs", flatSrcs)
   179  		}
   180  	}
   181  }
   182  
   183  // removeLegacyProto removes uses of the old proto rules. It deletes loads
   184  // from go_proto_library.bzl. It deletes proto filegroups. It removes
   185  // go_proto_library attributes which are no longer recognized. New rules
   186  // are generated in place of the deleted rules, but attributes and comments
   187  // are not migrated.
   188  func removeLegacyProto(c *config.Config, f *rule.File) {
   189  	// Don't fix if the proto mode was set to something other than the default.
   190  	pc := proto.GetProtoConfig(c)
   191  	if pc.Mode != proto.DefaultMode {
   192  		return
   193  	}
   194  
   195  	// Scan for definitions to delete.
   196  	var protoLoads []*rule.Load
   197  	for _, l := range f.Loads {
   198  		if l.Name() == "@io_bazel_rules_go//proto:go_proto_library.bzl" {
   199  			protoLoads = append(protoLoads, l)
   200  		}
   201  	}
   202  	var protoFilegroups, protoRules []*rule.Rule
   203  	for _, r := range f.Rules {
   204  		if r.Kind() == "filegroup" && r.Name() == legacyProtoFilegroupName {
   205  			protoFilegroups = append(protoFilegroups, r)
   206  		}
   207  		if r.Kind() == "go_proto_library" {
   208  			protoRules = append(protoRules, r)
   209  		}
   210  	}
   211  	if len(protoLoads)+len(protoFilegroups) == 0 {
   212  		return
   213  	}
   214  	if !c.ShouldFix {
   215  		log.Printf("%s: go_proto_library.bzl is deprecated. Run 'gazelle fix' to replace old rules.", f.Path)
   216  		return
   217  	}
   218  
   219  	// Delete legacy proto loads and filegroups. Only delete go_proto_library
   220  	// rules if we deleted a load.
   221  	for _, l := range protoLoads {
   222  		l.Delete()
   223  	}
   224  	for _, r := range protoFilegroups {
   225  		r.Delete()
   226  	}
   227  	if len(protoLoads) > 0 {
   228  		for _, r := range protoRules {
   229  			r.Delete()
   230  		}
   231  	}
   232  }
   233  
   234  // removeLegacyGazelle removes loads of the "gazelle" macro from
   235  // @io_bazel_rules_go//go:def.bzl. The definition has moved to
   236  // @bazel_gazelle//:def.bzl, and the old one will be deleted soon.
   237  func removeLegacyGazelle(c *config.Config, f *rule.File) {
   238  	for _, l := range f.Loads {
   239  		if l.Name() == "@io_bazel_rules_go//go:def.bzl" && l.Has("gazelle") {
   240  			l.Remove("gazelle")
   241  			if l.IsEmpty() {
   242  				l.Delete()
   243  			}
   244  		}
   245  	}
   246  }
   247  
   248  func isGoRule(kind string) bool {
   249  	return kind == "go_library" ||
   250  		kind == "go_binary" ||
   251  		kind == "go_test" ||
   252  		kind == "go_proto_library" ||
   253  		kind == "go_grpc_library"
   254  }