1
15
16 package proto
17
18 import (
19 "fmt"
20 "log"
21 "path"
22 "sort"
23 "strings"
24
25 "github.com/bazelbuild/bazel-gazelle/config"
26 "github.com/bazelbuild/bazel-gazelle/language"
27 "github.com/bazelbuild/bazel-gazelle/pathtools"
28 "github.com/bazelbuild/bazel-gazelle/rule"
29 )
30
31 func (*protoLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
32 c := args.Config
33 pc := GetProtoConfig(c)
34 if !pc.Mode.ShouldGenerateRules() {
35
36
37 return language.GenerateResult{}
38 }
39
40 var regularProtoFiles []string
41 for _, name := range args.RegularFiles {
42 if strings.HasSuffix(name, ".proto") {
43 regularProtoFiles = append(regularProtoFiles, name)
44 }
45 }
46
47
48 consumedFileSet := make(map[string]bool)
49 for _, r := range args.OtherGen {
50 if r.Kind() != "proto_library" {
51 continue
52 }
53 for _, f := range r.AttrStrings("srcs") {
54 consumedFileSet[f] = true
55 }
56 }
57
58
59
60
61
62 var genProtoFiles, genProtoFilesNotConsumed []string
63 for _, name := range args.GenFiles {
64 if strings.HasSuffix(name, ".proto") {
65 genProtoFiles = append(genProtoFiles, name)
66 if !consumedFileSet[name] {
67 genProtoFilesNotConsumed = append(genProtoFilesNotConsumed, name)
68 }
69 }
70 }
71 pkgs := buildPackages(pc, args.Dir, args.Rel, regularProtoFiles, genProtoFilesNotConsumed)
72 shouldSetVisibility := args.File == nil || !args.File.HasDefaultVisibility()
73 var res language.GenerateResult
74 for _, pkg := range pkgs {
75 r := generateProto(pc, args.Rel, pkg, shouldSetVisibility)
76 if r.IsEmpty(protoKinds[r.Kind()]) {
77 res.Empty = append(res.Empty, r)
78 } else {
79 res.Gen = append(res.Gen, r)
80 }
81 }
82 sort.SliceStable(res.Gen, func(i, j int) bool {
83 return res.Gen[i].Name() < res.Gen[j].Name()
84 })
85 res.Imports = make([]interface{}, len(res.Gen))
86 for i, r := range res.Gen {
87 res.Imports[i] = r.PrivateAttr(config.GazelleImportsKey)
88 }
89 res.Empty = append(res.Empty, generateEmpty(args.File, regularProtoFiles, genProtoFiles)...)
90 return res
91 }
92
93
94
95
96 func RuleName(names ...string) string {
97 base := "root"
98 for _, name := range names {
99 notIdent := func(c rune) bool {
100 return !('A' <= c && c <= 'Z' ||
101 'a' <= c && c <= 'z' ||
102 '0' <= c && c <= '9' ||
103 c == '_')
104 }
105 if i := strings.LastIndexFunc(name, notIdent); i >= 0 {
106 name = name[i+1:]
107 }
108 if name != "" {
109 base = name
110 break
111 }
112 }
113 return base + "_proto"
114 }
115
116
117
118
119 func buildPackages(pc *ProtoConfig, dir, rel string, protoFiles, genFiles []string) []*Package {
120 packageMap := make(map[string]*Package)
121 for _, name := range protoFiles {
122 info := protoFileInfo(dir, name)
123 key := info.PackageName
124
125 if pc.Mode == FileMode {
126 key = strings.TrimSuffix(name, ".proto")
127 } else if pc.groupOption != "" {
128 for _, opt := range info.Options {
129 if opt.Key == pc.groupOption {
130 key = opt.Value
131 break
132 }
133 }
134 }
135
136 if packageMap[key] == nil {
137 packageMap[key] = newPackage(info.PackageName)
138 }
139 packageMap[key].addFile(info)
140 if key != info.PackageName {
141 packageMap[key].RuleName = key
142 }
143 }
144
145 switch pc.Mode {
146 case DefaultMode:
147 pkg, err := selectPackage(dir, rel, packageMap)
148 if err != nil {
149 log.Print(err)
150 }
151 if pkg == nil {
152 return nil
153 }
154 for _, name := range genFiles {
155 pkg.addGenFile(dir, name)
156 }
157 return []*Package{pkg}
158
159 case PackageMode, FileMode:
160 pkgs := make([]*Package, 0, len(packageMap))
161 for _, pkg := range packageMap {
162 pkgs = append(pkgs, pkg)
163 }
164 return pkgs
165
166 default:
167 return nil
168 }
169 }
170
171
172 func selectPackage(dir, rel string, packageMap map[string]*Package) (*Package, error) {
173 if len(packageMap) == 0 {
174 return nil, nil
175 }
176 if len(packageMap) == 1 {
177 for _, pkg := range packageMap {
178 return pkg, nil
179 }
180 }
181 defaultPackageName := strings.Replace(rel, "/", "_", -1)
182 for _, pkg := range packageMap {
183 if pkgName := goPackageName(pkg); pkgName != "" && pkgName == defaultPackageName {
184 return pkg, nil
185 }
186 }
187 return nil, fmt.Errorf("%s: directory contains multiple proto packages. Gazelle can only generate a proto_library for one package.", dir)
188 }
189
190
191
192
193
194
195
196 func goPackageName(pkg *Package) string {
197 if opt, ok := pkg.Options["go_package"]; ok {
198 if i := strings.IndexByte(opt, ';'); i >= 0 {
199 return opt[i+1:]
200 } else if i := strings.LastIndexByte(opt, '/'); i >= 0 {
201 return opt[i+1:]
202 } else {
203 return opt
204 }
205 }
206 if pkg.Name != "" {
207 return strings.Replace(pkg.Name, ".", "_", -1)
208 }
209 if len(pkg.Files) == 1 {
210 for s := range pkg.Files {
211 return strings.TrimSuffix(s, ".proto")
212 }
213 }
214 return ""
215 }
216
217
218
219 func generateProto(pc *ProtoConfig, rel string, pkg *Package, shouldSetVisibility bool) *rule.Rule {
220 var name string
221 if pc.Mode == DefaultMode {
222 name = RuleName(goPackageName(pkg), pc.GoPrefix, rel)
223 } else {
224 name = RuleName(pkg.RuleName, pkg.Name, rel)
225 }
226 r := rule.NewRule("proto_library", name)
227 srcs := make([]string, 0, len(pkg.Files))
228 for f := range pkg.Files {
229 srcs = append(srcs, f)
230 }
231 sort.Strings(srcs)
232 if len(srcs) > 0 {
233 r.SetAttr("srcs", srcs)
234 }
235 r.SetPrivateAttr(PackageKey, *pkg)
236 imports := make([]string, 0, len(pkg.Imports))
237 for i := range pkg.Imports {
238
239 if _, ok := pkg.Files[path.Base(i)]; ok && getPrefix(pc, path.Dir(i)) == getPrefix(pc, rel) {
240 delete(pkg.Imports, i)
241 continue
242 }
243 imports = append(imports, i)
244 }
245 sort.Strings(imports)
246
247
248 r.SetPrivateAttr(config.GazelleImportsKey, imports)
249 for k, v := range pkg.Options {
250 r.SetPrivateAttr(k, v)
251 }
252 if shouldSetVisibility {
253 vis := rule.CheckInternalVisibility(rel, "//visibility:public")
254 r.SetAttr("visibility", []string{vis})
255 }
256 if pc.StripImportPrefix != "" {
257 r.SetAttr("strip_import_prefix", pc.StripImportPrefix)
258 }
259 if pc.ImportPrefix != "" {
260 r.SetAttr("import_prefix", pc.ImportPrefix)
261 }
262 return r
263 }
264
265 func getPrefix(pc *ProtoConfig, rel string) string {
266 prefix := rel
267 if strings.HasPrefix(pc.StripImportPrefix, "/") {
268 prefix = pathtools.TrimPrefix(rel, pc.StripImportPrefix[len("/"):])
269 } else if pc.StripImportPrefix != "" {
270 prefix = pathtools.TrimPrefix(rel, path.Join(rel, pc.StripImportPrefix))
271 }
272 if pc.ImportPrefix != "" {
273 return path.Join(pc.ImportPrefix, prefix)
274 }
275 return prefix
276 }
277
278
279
280
281 func generateEmpty(f *rule.File, regularFiles, genFiles []string) []*rule.Rule {
282 if f == nil {
283 return nil
284 }
285 knownFiles := make(map[string]bool)
286 for _, f := range regularFiles {
287 knownFiles[f] = true
288 }
289 for _, f := range genFiles {
290 knownFiles[f] = true
291 }
292 var empty []*rule.Rule
293 outer:
294 for _, r := range f.Rules {
295 if r.Kind() != "proto_library" {
296 continue
297 }
298 srcs := r.AttrStrings("srcs")
299 if len(srcs) == 0 && r.Attr("srcs") != nil {
300
301 continue
302 }
303 for _, src := range r.AttrStrings("srcs") {
304 if knownFiles[src] {
305 continue outer
306 }
307 }
308 empty = append(empty, rule.NewRule("proto_library", r.Name()))
309 }
310 return empty
311 }
312
View as plain text