| /* Copyright 2017 The Bazel Authors. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package golang |
| |
| import ( |
| "log" |
| |
| "github.com/bazelbuild/bazel-gazelle/config" |
| "github.com/bazelbuild/bazel-gazelle/language/proto" |
| "github.com/bazelbuild/bazel-gazelle/rule" |
| bzl "github.com/bazelbuild/buildtools/build" |
| ) |
| |
| func (*goLang) Fix(c *config.Config, f *rule.File) { |
| migrateLibraryEmbed(c, f) |
| migrateGrpcCompilers(c, f) |
| flattenSrcs(c, f) |
| squashCgoLibrary(c, f) |
| squashXtest(c, f) |
| removeLegacyProto(c, f) |
| removeLegacyGazelle(c, f) |
| migrateNamingConvention(c, f) |
| } |
| |
| // migrateNamingConvention renames rules according to go_naming_convention |
| // directives. |
| func migrateNamingConvention(c *config.Config, f *rule.File) { |
| // Determine old and new names for go_library and go_test. |
| nc := getGoConfig(c).goNamingConvention |
| importPath := InferImportPath(c, f.Pkg) |
| if importPath == "" { |
| return |
| } |
| var pkgName string // unknown unless there's a binary |
| if fileContainsGoBinary(c, f) { |
| pkgName = "main" |
| } |
| libName := libNameByConvention(nc, importPath, pkgName) |
| testName := testNameByConvention(nc, importPath) |
| var migrateLibName, migrateTestName string |
| switch nc { |
| case goDefaultLibraryNamingConvention: |
| migrateLibName = libNameByConvention(importNamingConvention, importPath, pkgName) |
| migrateTestName = testNameByConvention(importNamingConvention, importPath) |
| case importNamingConvention, importAliasNamingConvention: |
| migrateLibName = defaultLibName |
| migrateTestName = defaultTestName |
| default: |
| return |
| } |
| |
| // Check whether the new names are in use. If there are rules with both old |
| // and new names, there will be a conflict. |
| var haveLib, haveMigrateLib, haveTest, haveMigrateTest bool |
| for _, r := range f.Rules { |
| switch { |
| case r.Name() == libName: |
| haveLib = true |
| case r.Kind() == "go_library" && r.Name() == migrateLibName && r.AttrString("importpath") == importPath: |
| haveMigrateLib = true |
| case r.Name() == testName: |
| haveTest = true |
| case r.Kind() == "go_test" && r.Name() == migrateTestName && strListAttrContains(r, "embed", ":"+migrateLibName): |
| haveMigrateTest = true |
| } |
| } |
| if haveLib && haveMigrateLib { |
| log.Printf("%[1]s: Tried to rename %[2]s to %[3]s, but %[3]s already exists.", f.Path, migrateLibName, libName) |
| } |
| if haveTest && haveMigrateTest { |
| log.Printf("%[1]s: Tried to rename %[2]s to %[3]s, but %[3]s already exists.", f.Path, migrateTestName, testName) |
| } |
| shouldMigrateLib := haveMigrateLib && !haveLib |
| shouldMigrateTest := haveMigrateTest && !haveTest |
| |
| // Rename the targets and stuff in the same file that refers to them. |
| for _, r := range f.Rules { |
| // TODO(jayconrod): support map_kind directive. |
| // We'll need to move the metaresolver from resolve.RuleIndex to config.Config so we can access it from here. |
| switch r.Kind() { |
| case "go_binary": |
| if haveMigrateLib && shouldMigrateLib { |
| replaceInStrListAttr(r, "embed", ":"+migrateLibName, ":"+libName) |
| } |
| case "go_library": |
| if r.Name() == migrateLibName && shouldMigrateLib { |
| r.SetName(libName) |
| } |
| case "go_test": |
| if r.Name() == migrateTestName && shouldMigrateTest { |
| r.SetName(testName) |
| } |
| if shouldMigrateLib { |
| replaceInStrListAttr(r, "embed", ":"+migrateLibName, ":"+libName) |
| } |
| } |
| } |
| } |
| |
| // fileContainsGoBinary returns whether the file has a go_binary rule. |
| func fileContainsGoBinary(c *config.Config, f *rule.File) bool { |
| if f == nil { |
| return false |
| } |
| for _, r := range f.Rules { |
| kind := r.Kind() |
| if kind == "go_binary" { |
| return true |
| } |
| |
| if mappedKind, ok := c.KindMap["go_binary"]; ok { |
| if mappedKind.KindName == kind { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| |
| func replaceInStrListAttr(r *rule.Rule, attr, old, new string) { |
| items := r.AttrStrings(attr) |
| changed := false |
| for i := range items { |
| if items[i] == old { |
| changed = true |
| items[i] = new |
| } |
| } |
| if changed { |
| r.SetAttr(attr, items) |
| } |
| } |
| |
| func strListAttrContains(r *rule.Rule, attr, s string) bool { |
| items := r.AttrStrings(attr) |
| for _, item := range items { |
| if item == s { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // migrateLibraryEmbed converts "library" attributes to "embed" attributes, |
| // preserving comments. This only applies to Go rules, and only if there is |
| // no keep comment on "library" and no existing "embed" attribute. |
| func migrateLibraryEmbed(c *config.Config, f *rule.File) { |
| for _, r := range f.Rules { |
| if !isGoRule(r.Kind()) { |
| continue |
| } |
| libExpr := r.Attr("library") |
| if libExpr == nil || rule.ShouldKeep(libExpr) || r.Attr("embed") != nil { |
| continue |
| } |
| r.DelAttr("library") |
| r.SetAttr("embed", &bzl.ListExpr{List: []bzl.Expr{libExpr}}) |
| } |
| } |
| |
| // migrateGrpcCompilers converts "go_grpc_library" rules into "go_proto_library" |
| // rules with a "compilers" attribute. |
| func migrateGrpcCompilers(c *config.Config, f *rule.File) { |
| for _, r := range f.Rules { |
| if r.Kind() != "go_grpc_library" || r.ShouldKeep() || r.Attr("compilers") != nil { |
| continue |
| } |
| r.SetKind("go_proto_library") |
| r.SetAttr("compilers", []string{grpcCompilerLabel}) |
| } |
| } |
| |
| // squashCgoLibrary removes cgo_library rules with the default name and |
| // merges their attributes with go_library with the default name. If no |
| // go_library rule exists, a new one will be created. |
| // |
| // Note that the library attribute is disregarded, so cgo_library and |
| // go_library attributes will be squashed even if the cgo_library was unlinked. |
| // MergeFile will remove unused values and attributes later. |
| func squashCgoLibrary(c *config.Config, f *rule.File) { |
| // Find the default cgo_library and go_library rules. |
| var cgoLibrary, goLibrary *rule.Rule |
| for _, r := range f.Rules { |
| if r.Kind() == "cgo_library" && r.Name() == "cgo_default_library" && !r.ShouldKeep() { |
| if cgoLibrary != nil { |
| log.Printf("%s: when fixing existing file, multiple cgo_library rules with default name found", f.Path) |
| continue |
| } |
| cgoLibrary = r |
| continue |
| } |
| if r.Kind() == "go_library" && r.Name() == defaultLibName { |
| if goLibrary != nil { |
| log.Printf("%s: when fixing existing file, multiple go_library rules with default name referencing cgo_library found", f.Path) |
| } |
| goLibrary = r |
| continue |
| } |
| } |
| |
| if cgoLibrary == nil { |
| return |
| } |
| if !c.ShouldFix { |
| log.Printf("%s: cgo_library is deprecated. Run 'gazelle fix' to squash with go_library.", f.Path) |
| return |
| } |
| |
| if goLibrary == nil { |
| cgoLibrary.SetKind("go_library") |
| cgoLibrary.SetName(defaultLibName) |
| cgoLibrary.SetAttr("cgo", true) |
| return |
| } |
| |
| if err := rule.SquashRules(cgoLibrary, goLibrary, f.Path); err != nil { |
| log.Print(err) |
| return |
| } |
| goLibrary.DelAttr("embed") |
| goLibrary.SetAttr("cgo", true) |
| cgoLibrary.Delete() |
| } |
| |
| // squashXtest removes go_test rules with the default external name and merges |
| // their attributes with a go_test rule with the default internal name. If |
| // no internal go_test rule exists, a new one will be created (effectively |
| // renaming the old rule). |
| func squashXtest(c *config.Config, f *rule.File) { |
| // Search for internal and external tests. |
| var itest, xtest *rule.Rule |
| for _, r := range f.Rules { |
| if r.Kind() != "go_test" { |
| continue |
| } |
| if r.Name() == defaultTestName { |
| itest = r |
| } else if r.Name() == "go_default_xtest" { |
| xtest = r |
| } |
| } |
| |
| if xtest == nil || xtest.ShouldKeep() || (itest != nil && itest.ShouldKeep()) { |
| return |
| } |
| if !c.ShouldFix { |
| if itest == nil { |
| log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to rename to go_default_test.", f.Path) |
| } else { |
| log.Printf("%s: go_default_xtest is no longer necessary. Run 'gazelle fix' to squash with go_default_test.", f.Path) |
| } |
| return |
| } |
| |
| // If there was no internal test, we can just rename the external test. |
| if itest == nil { |
| xtest.SetName(defaultTestName) |
| return |
| } |
| |
| // Attempt to squash. |
| if err := rule.SquashRules(xtest, itest, f.Path); err != nil { |
| log.Print(err) |
| return |
| } |
| xtest.Delete() |
| } |
| |
| // flattenSrcs transforms srcs attributes structured as concatenations of |
| // lists and selects (generated from PlatformStrings; see |
| // extractPlatformStringsExprs for matching details) into a sorted, |
| // de-duplicated list. Comments are accumulated and de-duplicated across |
| // duplicate expressions. |
| func flattenSrcs(c *config.Config, f *rule.File) { |
| for _, r := range f.Rules { |
| if !isGoRule(r.Kind()) { |
| continue |
| } |
| oldSrcs := r.Attr("srcs") |
| if oldSrcs == nil { |
| continue |
| } |
| flatSrcs := rule.FlattenExpr(oldSrcs) |
| if flatSrcs != oldSrcs { |
| r.SetAttr("srcs", flatSrcs) |
| } |
| } |
| } |
| |
| // removeLegacyProto removes uses of the old proto rules. It deletes loads |
| // from go_proto_library.bzl. It deletes proto filegroups. It removes |
| // go_proto_library attributes which are no longer recognized. New rules |
| // are generated in place of the deleted rules, but attributes and comments |
| // are not migrated. |
| func removeLegacyProto(c *config.Config, f *rule.File) { |
| // Don't fix if the proto mode was set to something other than the default. |
| if pcMode := getProtoMode(c); pcMode != proto.DefaultMode { |
| return |
| } |
| |
| // Scan for definitions to delete. |
| var protoLoads []*rule.Load |
| for _, l := range f.Loads { |
| if l.Name() == "@io_bazel_rules_go//proto:go_proto_library.bzl" { |
| protoLoads = append(protoLoads, l) |
| } |
| } |
| var protoFilegroups, protoRules []*rule.Rule |
| for _, r := range f.Rules { |
| if r.Kind() == "filegroup" && r.Name() == legacyProtoFilegroupName { |
| protoFilegroups = append(protoFilegroups, r) |
| } |
| if r.Kind() == "go_proto_library" { |
| protoRules = append(protoRules, r) |
| } |
| } |
| if len(protoLoads)+len(protoFilegroups) == 0 { |
| return |
| } |
| if !c.ShouldFix { |
| log.Printf("%s: go_proto_library.bzl is deprecated. Run 'gazelle fix' to replace old rules.", f.Path) |
| return |
| } |
| |
| // Delete legacy proto loads and filegroups. Only delete go_proto_library |
| // rules if we deleted a load. |
| for _, l := range protoLoads { |
| l.Delete() |
| } |
| for _, r := range protoFilegroups { |
| r.Delete() |
| } |
| if len(protoLoads) > 0 { |
| for _, r := range protoRules { |
| r.Delete() |
| } |
| } |
| } |
| |
| // removeLegacyGazelle removes loads of the "gazelle" macro from |
| // @io_bazel_rules_go//go:def.bzl. The definition has moved to |
| // @bazel_gazelle//:def.bzl, and the old one will be deleted soon. |
| func removeLegacyGazelle(c *config.Config, f *rule.File) { |
| for _, l := range f.Loads { |
| if l.Name() == "@io_bazel_rules_go//go:def.bzl" && l.Has("gazelle") { |
| l.Remove("gazelle") |
| if l.IsEmpty() { |
| l.Delete() |
| } |
| } |
| } |
| } |
| |
| func isGoRule(kind string) bool { |
| return kind == "go_library" || |
| kind == "go_binary" || |
| kind == "go_test" || |
| kind == "go_proto_library" || |
| kind == "go_grpc_library" |
| } |