github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/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/config" 22 "github.com/bazelbuild/bazel-gazelle/language/proto" 23 "github.com/bazelbuild/bazel-gazelle/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 migrateNamingConvention(c, f) 36 } 37 38 // migrateNamingConvention renames rules according to go_naming_convention 39 // directives. 40 func migrateNamingConvention(c *config.Config, f *rule.File) { 41 // Determine old and new names for go_library and go_test. 42 nc := getGoConfig(c).goNamingConvention 43 importPath := InferImportPath(c, f.Pkg) 44 if importPath == "" { 45 return 46 } 47 var pkgName string // unknown unless there's a binary 48 if fileContainsGoBinary(c, f) { 49 pkgName = "main" 50 } 51 libName := libNameByConvention(nc, importPath, pkgName) 52 testName := testNameByConvention(nc, importPath) 53 var migrateLibName, migrateTestName string 54 switch nc { 55 case goDefaultLibraryNamingConvention: 56 migrateLibName = libNameByConvention(importNamingConvention, importPath, pkgName) 57 migrateTestName = testNameByConvention(importNamingConvention, importPath) 58 case importNamingConvention, importAliasNamingConvention: 59 migrateLibName = defaultLibName 60 migrateTestName = defaultTestName 61 default: 62 return 63 } 64 65 // Check whether the new names are in use. If there are rules with both old 66 // and new names, there will be a conflict. 67 var haveLib, haveMigrateLib, haveTest, haveMigrateTest bool 68 for _, r := range f.Rules { 69 switch { 70 case r.Name() == libName: 71 haveLib = true 72 case r.Kind() == "go_library" && r.Name() == migrateLibName && r.AttrString("importpath") == importPath: 73 haveMigrateLib = true 74 case r.Name() == testName: 75 haveTest = true 76 case r.Kind() == "go_test" && r.Name() == migrateTestName && strListAttrContains(r, "embed", ":"+migrateLibName): 77 haveMigrateTest = true 78 } 79 } 80 if haveLib && haveMigrateLib { 81 log.Printf("%[1]s: Tried to rename %[2]s to %[3]s, but %[3]s already exists.", f.Path, migrateLibName, libName) 82 } 83 if haveTest && haveMigrateTest { 84 log.Printf("%[1]s: Tried to rename %[2]s to %[3]s, but %[3]s already exists.", f.Path, migrateTestName, testName) 85 } 86 shouldMigrateLib := haveMigrateLib && !haveLib 87 shouldMigrateTest := haveMigrateTest && !haveTest 88 89 // Rename the targets and stuff in the same file that refers to them. 90 for _, r := range f.Rules { 91 // TODO(jayconrod): support map_kind directive. 92 // We'll need to move the metaresolver from resolve.RuleIndex to config.Config so we can access it from here. 93 switch r.Kind() { 94 case "go_binary": 95 if haveMigrateLib && shouldMigrateLib { 96 replaceInStrListAttr(r, "embed", ":"+migrateLibName, ":"+libName) 97 } 98 case "go_library": 99 if r.Name() == migrateLibName && shouldMigrateLib { 100 r.SetName(libName) 101 } 102 case "go_test": 103 if r.Name() == migrateTestName && shouldMigrateTest { 104 r.SetName(testName) 105 } 106 if shouldMigrateLib { 107 replaceInStrListAttr(r, "embed", ":"+migrateLibName, ":"+libName) 108 } 109 } 110 } 111 } 112 113 // fileContainsGoBinary returns whether the file has a go_binary rule. 114 func fileContainsGoBinary(c *config.Config, f *rule.File) bool { 115 if f == nil { 116 return false 117 } 118 for _, r := range f.Rules { 119 kind := r.Kind() 120 if kind == "go_binary" { 121 return true 122 } 123 124 if mappedKind, ok := c.KindMap["go_binary"]; ok { 125 if mappedKind.KindName == kind { 126 return true 127 } 128 } 129 } 130 return false 131 } 132 133 func replaceInStrListAttr(r *rule.Rule, attr, old, new string) { 134 items := r.AttrStrings(attr) 135 changed := false 136 for i := range items { 137 if items[i] == old { 138 changed = true 139 items[i] = new 140 } 141 } 142 if changed { 143 r.SetAttr(attr, items) 144 } 145 } 146 147 func strListAttrContains(r *rule.Rule, attr, s string) bool { 148 items := r.AttrStrings(attr) 149 for _, item := range items { 150 if item == s { 151 return true 152 } 153 } 154 return false 155 } 156 157 // migrateLibraryEmbed converts "library" attributes to "embed" attributes, 158 // preserving comments. This only applies to Go rules, and only if there is 159 // no keep comment on "library" and no existing "embed" attribute. 160 func migrateLibraryEmbed(c *config.Config, f *rule.File) { 161 for _, r := range f.Rules { 162 if !isGoRule(r.Kind()) { 163 continue 164 } 165 libExpr := r.Attr("library") 166 if libExpr == nil || rule.ShouldKeep(libExpr) || r.Attr("embed") != nil { 167 continue 168 } 169 r.DelAttr("library") 170 r.SetAttr("embed", &bzl.ListExpr{List: []bzl.Expr{libExpr}}) 171 } 172 } 173 174 // migrateGrpcCompilers converts "go_grpc_library" rules into "go_proto_library" 175 // rules with a "compilers" attribute. 176 func migrateGrpcCompilers(c *config.Config, f *rule.File) { 177 for _, r := range f.Rules { 178 if r.Kind() != "go_grpc_library" || r.ShouldKeep() || r.Attr("compilers") != nil { 179 continue 180 } 181 r.SetKind("go_proto_library") 182 r.SetAttr("compilers", []string{grpcCompilerLabel}) 183 } 184 } 185 186 // squashCgoLibrary removes cgo_library rules with the default name and 187 // merges their attributes with go_library with the default name. If no 188 // go_library rule exists, a new one will be created. 189 // 190 // Note that the library attribute is disregarded, so cgo_library and 191 // go_library attributes will be squashed even if the cgo_library was unlinked. 192 // MergeFile will remove unused values and attributes later. 193 func squashCgoLibrary(c *config.Config, f *rule.File) { 194 // Find the default cgo_library and go_library rules. 195 var cgoLibrary, goLibrary *rule.Rule 196 for _, r := range f.Rules { 197 if r.Kind() == "cgo_library" && r.Name() == "cgo_default_library" && !r.ShouldKeep() { 198 if cgoLibrary != nil { 199 log.Printf("%s: when fixing existing file, multiple cgo_library rules with default name found", f.Path) 200 continue 201 } 202 cgoLibrary = r 203 continue 204 } 205 if r.Kind() == "go_library" && r.Name() == defaultLibName { 206 if goLibrary != nil { 207 log.Printf("%s: when fixing existing file, multiple go_library rules with default name referencing cgo_library found", f.Path) 208 } 209 goLibrary = r 210 continue 211 } 212 } 213 214 if cgoLibrary == nil { 215 return 216 } 217 if !c.ShouldFix { 218 log.Printf("%s: cgo_library is deprecated. Run 'gazelle fix' to squash with go_library.", f.Path) 219 return 220 } 221 222 if goLibrary == nil { 223 cgoLibrary.SetKind("go_library") 224 cgoLibrary.SetName(defaultLibName) 225 cgoLibrary.SetAttr("cgo", true) 226 return 227 } 228 229 if err := rule.SquashRules(cgoLibrary, goLibrary, f.Path); err != nil { 230 log.Print(err) 231 return 232 } 233 goLibrary.DelAttr("embed") 234 goLibrary.SetAttr("cgo", true) 235 cgoLibrary.Delete() 236 } 237 238 // squashXtest removes go_test rules with the default external name and merges 239 // their attributes with a go_test rule with the default internal name. If 240 // no internal go_test rule exists, a new one will be created (effectively 241 // renaming the old rule). 242 func squashXtest(c *config.Config, f *rule.File) { 243 // Search for internal and external tests. 244 var itest, xtest *rule.Rule 245 for _, r := range f.Rules { 246 if r.Kind() != "go_test" { 247 continue 248 } 249 if r.Name() == defaultTestName { 250 itest = r 251 } else if r.Name() == "go_default_xtest" { 252 xtest = r 253 } 254 } 255 256 if xtest == nil || xtest.ShouldKeep() || (itest != nil && itest.ShouldKeep()) { 257 return 258 } 259 if !c.ShouldFix { 260 if itest == nil { 261 log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to rename to go_default_test.", f.Path) 262 } else { 263 log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to squash with go_default_test.", f.Path) 264 } 265 return 266 } 267 268 // If there was no internal test, we can just rename the external test. 269 if itest == nil { 270 xtest.SetName(defaultTestName) 271 return 272 } 273 274 // Attempt to squash. 275 if err := rule.SquashRules(xtest, itest, f.Path); err != nil { 276 log.Print(err) 277 return 278 } 279 xtest.Delete() 280 } 281 282 // flattenSrcs transforms srcs attributes structured as concatenations of 283 // lists and selects (generated from PlatformStrings; see 284 // extractPlatformStringsExprs for matching details) into a sorted, 285 // de-duplicated list. Comments are accumulated and de-duplicated across 286 // duplicate expressions. 287 func flattenSrcs(c *config.Config, f *rule.File) { 288 for _, r := range f.Rules { 289 if !isGoRule(r.Kind()) { 290 continue 291 } 292 oldSrcs := r.Attr("srcs") 293 if oldSrcs == nil { 294 continue 295 } 296 flatSrcs := rule.FlattenExpr(oldSrcs) 297 if flatSrcs != oldSrcs { 298 r.SetAttr("srcs", flatSrcs) 299 } 300 } 301 } 302 303 // removeLegacyProto removes uses of the old proto rules. It deletes loads 304 // from go_proto_library.bzl. It deletes proto filegroups. It removes 305 // go_proto_library attributes which are no longer recognized. New rules 306 // are generated in place of the deleted rules, but attributes and comments 307 // are not migrated. 308 func removeLegacyProto(c *config.Config, f *rule.File) { 309 // Don't fix if the proto mode was set to something other than the default. 310 if pcMode := getProtoMode(c); pcMode != proto.DefaultMode { 311 return 312 } 313 314 // Scan for definitions to delete. 315 var protoLoads []*rule.Load 316 for _, l := range f.Loads { 317 if l.Name() == "@io_bazel_rules_go//proto:go_proto_library.bzl" { 318 protoLoads = append(protoLoads, l) 319 } 320 } 321 var protoFilegroups, protoRules []*rule.Rule 322 for _, r := range f.Rules { 323 if r.Kind() == "filegroup" && r.Name() == legacyProtoFilegroupName { 324 protoFilegroups = append(protoFilegroups, r) 325 } 326 if r.Kind() == "go_proto_library" { 327 protoRules = append(protoRules, r) 328 } 329 } 330 if len(protoLoads)+len(protoFilegroups) == 0 { 331 return 332 } 333 if !c.ShouldFix { 334 log.Printf("%s: go_proto_library.bzl is deprecated. Run 'gazelle fix' to replace old rules.", f.Path) 335 return 336 } 337 338 // Delete legacy proto loads and filegroups. Only delete go_proto_library 339 // rules if we deleted a load. 340 for _, l := range protoLoads { 341 l.Delete() 342 } 343 for _, r := range protoFilegroups { 344 r.Delete() 345 } 346 if len(protoLoads) > 0 { 347 for _, r := range protoRules { 348 r.Delete() 349 } 350 } 351 } 352 353 // removeLegacyGazelle removes loads of the "gazelle" macro from 354 // @io_bazel_rules_go//go:def.bzl. The definition has moved to 355 // @bazel_gazelle//:def.bzl, and the old one will be deleted soon. 356 func removeLegacyGazelle(c *config.Config, f *rule.File) { 357 for _, l := range f.Loads { 358 if l.Name() == "@io_bazel_rules_go//go:def.bzl" && l.Has("gazelle") { 359 l.Remove("gazelle") 360 if l.IsEmpty() { 361 l.Delete() 362 } 363 } 364 } 365 } 366 367 func isGoRule(kind string) bool { 368 return kind == "go_library" || 369 kind == "go_binary" || 370 kind == "go_test" || 371 kind == "go_proto_library" || 372 kind == "go_grpc_library" 373 }