generate.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* Copyright 2018 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package proto
  13. import (
  14. "fmt"
  15. "log"
  16. "sort"
  17. "strings"
  18. "github.com/bazelbuild/bazel-gazelle/internal/config"
  19. "github.com/bazelbuild/bazel-gazelle/internal/rule"
  20. )
  21. func (_ *protoLang) GenerateRules(c *config.Config, dir, rel string, f *rule.File, subdirs, regularFiles, genFiles []string, otherEmpty, otherGen []*rule.Rule) (empty, gen []*rule.Rule) {
  22. pc := GetProtoConfig(c)
  23. if !pc.Mode.ShouldGenerateRules() {
  24. // Don't create or delete proto rules in this mode. Any existing rules
  25. // are likely hand-written.
  26. return nil, nil
  27. }
  28. var regularProtoFiles []string
  29. for _, name := range regularFiles {
  30. if strings.HasSuffix(name, ".proto") {
  31. regularProtoFiles = append(regularProtoFiles, name)
  32. }
  33. }
  34. var genProtoFiles []string
  35. for _, name := range genFiles {
  36. if strings.HasSuffix(name, ".proto") {
  37. genProtoFiles = append(genFiles, name)
  38. }
  39. }
  40. pkgs := buildPackages(pc, dir, rel, regularProtoFiles, genProtoFiles)
  41. shouldSetVisibility := !hasDefaultVisibility(f)
  42. for _, pkg := range pkgs {
  43. r := generateProto(pc, rel, pkg, shouldSetVisibility)
  44. if r.IsEmpty(protoKinds[r.Kind()]) {
  45. empty = append(empty, r)
  46. } else {
  47. gen = append(gen, r)
  48. }
  49. }
  50. sort.SliceStable(gen, func(i, j int) bool {
  51. return gen[i].Name() < gen[j].Name()
  52. })
  53. empty = append(empty, generateEmpty(f, regularProtoFiles, genProtoFiles)...)
  54. return empty, gen
  55. }
  56. // RuleName returns a name for a proto_library derived from the given strings.
  57. // For each string, RuleName will look for a non-empty suffix of identifier
  58. // characters and then append "_proto" to that.
  59. func RuleName(names ...string) string {
  60. base := "root"
  61. for _, name := range names {
  62. notIdent := func(c rune) bool {
  63. return !('A' <= c && c <= 'Z' ||
  64. 'a' <= c && c <= 'z' ||
  65. '0' <= c && c <= '9' ||
  66. c == '_')
  67. }
  68. if i := strings.LastIndexFunc(name, notIdent); i >= 0 {
  69. name = name[i+1:]
  70. }
  71. if name != "" {
  72. base = name
  73. break
  74. }
  75. }
  76. return base + "_proto"
  77. }
  78. // buildPackage extracts metadata from the .proto files in a directory and
  79. // constructs possibly several packages, then selects a package to generate
  80. // a proto_library rule for.
  81. func buildPackages(pc *ProtoConfig, dir, rel string, protoFiles, genFiles []string) []*Package {
  82. packageMap := make(map[string]*Package)
  83. for _, name := range protoFiles {
  84. info := protoFileInfo(dir, name)
  85. key := info.PackageName
  86. if pc.groupOption != "" {
  87. for _, opt := range info.Options {
  88. if opt.Key == pc.groupOption {
  89. key = opt.Value
  90. break
  91. }
  92. }
  93. }
  94. if packageMap[key] == nil {
  95. packageMap[key] = newPackage(info.PackageName)
  96. }
  97. packageMap[key].addFile(info)
  98. }
  99. switch pc.Mode {
  100. case DefaultMode:
  101. pkg, err := selectPackage(dir, rel, packageMap)
  102. if err != nil {
  103. log.Print(err)
  104. }
  105. if pkg == nil {
  106. return nil // empty rule created in generateEmpty
  107. }
  108. for _, name := range genFiles {
  109. pkg.addGenFile(dir, name)
  110. }
  111. return []*Package{pkg}
  112. case PackageMode:
  113. pkgs := make([]*Package, 0, len(packageMap))
  114. for _, pkg := range packageMap {
  115. pkgs = append(pkgs, pkg)
  116. }
  117. return pkgs
  118. default:
  119. return nil
  120. }
  121. }
  122. // selectPackage chooses a package to generate rules for.
  123. func selectPackage(dir, rel string, packageMap map[string]*Package) (*Package, error) {
  124. if len(packageMap) == 0 {
  125. return nil, nil
  126. }
  127. if len(packageMap) == 1 {
  128. for _, pkg := range packageMap {
  129. return pkg, nil
  130. }
  131. }
  132. defaultPackageName := strings.Replace(rel, "/", "_", -1)
  133. for _, pkg := range packageMap {
  134. if pkgName := goPackageName(pkg); pkgName != "" && pkgName == defaultPackageName {
  135. return pkg, nil
  136. }
  137. }
  138. return nil, fmt.Errorf("%s: directory contains multiple proto packages. Gazelle can only generate a proto_library for one package.", dir)
  139. }
  140. // goPackageName guesses the identifier in package declarations at the top of
  141. // the .pb.go files that will be generated for this package. "" is returned
  142. // if the package name cannot be determined.
  143. //
  144. // TODO(jayconrod): remove all Go-specific functionality. This is here
  145. // temporarily for compatibility.
  146. func goPackageName(pkg *Package) string {
  147. if opt, ok := pkg.Options["go_package"]; ok {
  148. if i := strings.IndexByte(opt, ';'); i >= 0 {
  149. return opt[i+1:]
  150. } else if i := strings.LastIndexByte(opt, '/'); i >= 0 {
  151. return opt[i+1:]
  152. } else {
  153. return opt
  154. }
  155. }
  156. if pkg.Name != "" {
  157. return strings.Replace(pkg.Name, ".", "_", -1)
  158. }
  159. if len(pkg.Files) == 1 {
  160. for s := range pkg.Files {
  161. return strings.TrimSuffix(s, ".proto")
  162. }
  163. }
  164. return ""
  165. }
  166. // generateProto creates a new proto_library rule for a package. The rule may
  167. // be empty if there are no sources.
  168. func generateProto(pc *ProtoConfig, rel string, pkg *Package, shouldSetVisibility bool) *rule.Rule {
  169. var name string
  170. if pc.Mode == DefaultMode {
  171. name = RuleName(goPackageName(pkg), pc.GoPrefix, rel)
  172. } else {
  173. name = RuleName(pkg.Options[pc.groupOption], pkg.Name, rel)
  174. }
  175. r := rule.NewRule("proto_library", name)
  176. srcs := make([]string, 0, len(pkg.Files))
  177. for f := range pkg.Files {
  178. srcs = append(srcs, f)
  179. }
  180. sort.Strings(srcs)
  181. if len(srcs) > 0 {
  182. r.SetAttr("srcs", srcs)
  183. }
  184. r.SetPrivateAttr(PackageKey, *pkg)
  185. imports := make([]string, 0, len(pkg.Imports))
  186. for i := range pkg.Imports {
  187. imports = append(imports, i)
  188. }
  189. sort.Strings(imports)
  190. r.SetPrivateAttr(config.GazelleImportsKey, imports)
  191. for k, v := range pkg.Options {
  192. r.SetPrivateAttr(k, v)
  193. }
  194. if shouldSetVisibility {
  195. vis := checkInternalVisibility(rel, "//visibility:public")
  196. r.SetAttr("visibility", []string{vis})
  197. }
  198. return r
  199. }
  200. // generateEmpty generates a list of proto_library rules that may be deleted.
  201. // This is generated from existing proto_library rules with srcs lists that
  202. // don't match any static or generated files.
  203. func generateEmpty(f *rule.File, regularFiles, genFiles []string) []*rule.Rule {
  204. if f == nil {
  205. return nil
  206. }
  207. knownFiles := make(map[string]bool)
  208. for _, f := range regularFiles {
  209. knownFiles[f] = true
  210. }
  211. for _, f := range genFiles {
  212. knownFiles[f] = true
  213. }
  214. var empty []*rule.Rule
  215. outer:
  216. for _, r := range f.Rules {
  217. if r.Kind() != "proto_library" {
  218. continue
  219. }
  220. srcs := r.AttrStrings("srcs")
  221. if len(srcs) == 0 && r.Attr("srcs") != nil {
  222. // srcs is not a string list; leave it alone
  223. continue
  224. }
  225. for _, src := range r.AttrStrings("srcs") {
  226. if knownFiles[src] {
  227. continue outer
  228. }
  229. }
  230. empty = append(empty, rule.NewRule("proto_library", r.Name()))
  231. }
  232. return empty
  233. }
  234. // hasDefaultVisibility returns whether oldFile contains a "package" rule with
  235. // a "default_visibility" attribute. Rules generated by Gazelle should not
  236. // have their own visibility attributes if this is the case.
  237. func hasDefaultVisibility(f *rule.File) bool {
  238. if f == nil {
  239. return false
  240. }
  241. for _, r := range f.Rules {
  242. if r.Kind() == "package" && r.Attr("default_visibility") != nil {
  243. return true
  244. }
  245. }
  246. return false
  247. }
  248. // checkInternalVisibility overrides the given visibility if the package is
  249. // internal.
  250. func checkInternalVisibility(rel, visibility string) string {
  251. if i := strings.LastIndex(rel, "/internal/"); i >= 0 {
  252. visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i])
  253. } else if strings.HasPrefix(rel, "internal/") {
  254. visibility = "//:__subpackages__"
  255. }
  256. return visibility
  257. }