123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- /* Copyright 2018 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 proto
- import (
- "fmt"
- "log"
- "sort"
- "strings"
- "github.com/bazelbuild/bazel-gazelle/internal/config"
- "github.com/bazelbuild/bazel-gazelle/internal/rule"
- )
- func (_ *protoLang) GenerateRules(c *config.Config, dir, rel string, f *rule.File, subdirs, regularFiles, genFiles []string, otherEmpty, otherGen []*rule.Rule) (empty, gen []*rule.Rule) {
- pc := GetProtoConfig(c)
- if !pc.Mode.ShouldGenerateRules() {
- // Don't create or delete proto rules in this mode. Any existing rules
- // are likely hand-written.
- return nil, nil
- }
- var regularProtoFiles []string
- for _, name := range regularFiles {
- if strings.HasSuffix(name, ".proto") {
- regularProtoFiles = append(regularProtoFiles, name)
- }
- }
- var genProtoFiles []string
- for _, name := range genFiles {
- if strings.HasSuffix(name, ".proto") {
- genProtoFiles = append(genFiles, name)
- }
- }
- pkgs := buildPackages(pc, dir, rel, regularProtoFiles, genProtoFiles)
- shouldSetVisibility := !hasDefaultVisibility(f)
- for _, pkg := range pkgs {
- r := generateProto(pc, rel, pkg, shouldSetVisibility)
- if r.IsEmpty(protoKinds[r.Kind()]) {
- empty = append(empty, r)
- } else {
- gen = append(gen, r)
- }
- }
- sort.SliceStable(gen, func(i, j int) bool {
- return gen[i].Name() < gen[j].Name()
- })
- empty = append(empty, generateEmpty(f, regularProtoFiles, genProtoFiles)...)
- return empty, gen
- }
- // RuleName returns a name for a proto_library derived from the given strings.
- // For each string, RuleName will look for a non-empty suffix of identifier
- // characters and then append "_proto" to that.
- func RuleName(names ...string) string {
- base := "root"
- for _, name := range names {
- notIdent := func(c rune) bool {
- return !('A' <= c && c <= 'Z' ||
- 'a' <= c && c <= 'z' ||
- '0' <= c && c <= '9' ||
- c == '_')
- }
- if i := strings.LastIndexFunc(name, notIdent); i >= 0 {
- name = name[i+1:]
- }
- if name != "" {
- base = name
- break
- }
- }
- return base + "_proto"
- }
- // buildPackage extracts metadata from the .proto files in a directory and
- // constructs possibly several packages, then selects a package to generate
- // a proto_library rule for.
- func buildPackages(pc *ProtoConfig, dir, rel string, protoFiles, genFiles []string) []*Package {
- packageMap := make(map[string]*Package)
- for _, name := range protoFiles {
- info := protoFileInfo(dir, name)
- key := info.PackageName
- if pc.groupOption != "" {
- for _, opt := range info.Options {
- if opt.Key == pc.groupOption {
- key = opt.Value
- break
- }
- }
- }
- if packageMap[key] == nil {
- packageMap[key] = newPackage(info.PackageName)
- }
- packageMap[key].addFile(info)
- }
- switch pc.Mode {
- case DefaultMode:
- pkg, err := selectPackage(dir, rel, packageMap)
- if err != nil {
- log.Print(err)
- }
- if pkg == nil {
- return nil // empty rule created in generateEmpty
- }
- for _, name := range genFiles {
- pkg.addGenFile(dir, name)
- }
- return []*Package{pkg}
- case PackageMode:
- pkgs := make([]*Package, 0, len(packageMap))
- for _, pkg := range packageMap {
- pkgs = append(pkgs, pkg)
- }
- return pkgs
- default:
- return nil
- }
- }
- // selectPackage chooses a package to generate rules for.
- func selectPackage(dir, rel string, packageMap map[string]*Package) (*Package, error) {
- if len(packageMap) == 0 {
- return nil, nil
- }
- if len(packageMap) == 1 {
- for _, pkg := range packageMap {
- return pkg, nil
- }
- }
- defaultPackageName := strings.Replace(rel, "/", "_", -1)
- for _, pkg := range packageMap {
- if pkgName := goPackageName(pkg); pkgName != "" && pkgName == defaultPackageName {
- return pkg, nil
- }
- }
- return nil, fmt.Errorf("%s: directory contains multiple proto packages. Gazelle can only generate a proto_library for one package.", dir)
- }
- // goPackageName guesses the identifier in package declarations at the top of
- // the .pb.go files that will be generated for this package. "" is returned
- // if the package name cannot be determined.
- //
- // TODO(jayconrod): remove all Go-specific functionality. This is here
- // temporarily for compatibility.
- func goPackageName(pkg *Package) string {
- if opt, ok := pkg.Options["go_package"]; ok {
- if i := strings.IndexByte(opt, ';'); i >= 0 {
- return opt[i+1:]
- } else if i := strings.LastIndexByte(opt, '/'); i >= 0 {
- return opt[i+1:]
- } else {
- return opt
- }
- }
- if pkg.Name != "" {
- return strings.Replace(pkg.Name, ".", "_", -1)
- }
- if len(pkg.Files) == 1 {
- for s := range pkg.Files {
- return strings.TrimSuffix(s, ".proto")
- }
- }
- return ""
- }
- // generateProto creates a new proto_library rule for a package. The rule may
- // be empty if there are no sources.
- func generateProto(pc *ProtoConfig, rel string, pkg *Package, shouldSetVisibility bool) *rule.Rule {
- var name string
- if pc.Mode == DefaultMode {
- name = RuleName(goPackageName(pkg), pc.GoPrefix, rel)
- } else {
- name = RuleName(pkg.Options[pc.groupOption], pkg.Name, rel)
- }
- r := rule.NewRule("proto_library", name)
- srcs := make([]string, 0, len(pkg.Files))
- for f := range pkg.Files {
- srcs = append(srcs, f)
- }
- sort.Strings(srcs)
- if len(srcs) > 0 {
- r.SetAttr("srcs", srcs)
- }
- r.SetPrivateAttr(PackageKey, *pkg)
- imports := make([]string, 0, len(pkg.Imports))
- for i := range pkg.Imports {
- imports = append(imports, i)
- }
- sort.Strings(imports)
- r.SetPrivateAttr(config.GazelleImportsKey, imports)
- for k, v := range pkg.Options {
- r.SetPrivateAttr(k, v)
- }
- if shouldSetVisibility {
- vis := checkInternalVisibility(rel, "//visibility:public")
- r.SetAttr("visibility", []string{vis})
- }
- return r
- }
- // generateEmpty generates a list of proto_library rules that may be deleted.
- // This is generated from existing proto_library rules with srcs lists that
- // don't match any static or generated files.
- func generateEmpty(f *rule.File, regularFiles, genFiles []string) []*rule.Rule {
- if f == nil {
- return nil
- }
- knownFiles := make(map[string]bool)
- for _, f := range regularFiles {
- knownFiles[f] = true
- }
- for _, f := range genFiles {
- knownFiles[f] = true
- }
- var empty []*rule.Rule
- outer:
- for _, r := range f.Rules {
- if r.Kind() != "proto_library" {
- continue
- }
- srcs := r.AttrStrings("srcs")
- if len(srcs) == 0 && r.Attr("srcs") != nil {
- // srcs is not a string list; leave it alone
- continue
- }
- for _, src := range r.AttrStrings("srcs") {
- if knownFiles[src] {
- continue outer
- }
- }
- empty = append(empty, rule.NewRule("proto_library", r.Name()))
- }
- return empty
- }
- // hasDefaultVisibility returns whether oldFile contains a "package" rule with
- // a "default_visibility" attribute. Rules generated by Gazelle should not
- // have their own visibility attributes if this is the case.
- func hasDefaultVisibility(f *rule.File) bool {
- if f == nil {
- return false
- }
- for _, r := range f.Rules {
- if r.Kind() == "package" && r.Attr("default_visibility") != nil {
- return true
- }
- }
- return false
- }
- // checkInternalVisibility overrides the given visibility if the package is
- // internal.
- func checkInternalVisibility(rel, visibility string) string {
- if i := strings.LastIndex(rel, "/internal/"); i >= 0 {
- visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i])
- } else if strings.HasPrefix(rel, "internal/") {
- visibility = "//:__subpackages__"
- }
- return visibility
- }
|