1
15
16 package golang
17
18 import (
19 "errors"
20 "fmt"
21 "go/build"
22 "log"
23 "path"
24 "strings"
25
26 "github.com/bazelbuild/bazel-gazelle/config"
27 "github.com/bazelbuild/bazel-gazelle/label"
28 "github.com/bazelbuild/bazel-gazelle/pathtools"
29 "github.com/bazelbuild/bazel-gazelle/repo"
30 "github.com/bazelbuild/bazel-gazelle/resolve"
31 "github.com/bazelbuild/bazel-gazelle/rule"
32 )
33
34 func (*goLang) Imports(_ *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
35 if !isGoLibrary(r.Kind()) || isExtraLibrary(r) {
36 return nil
37 }
38 if importPath := r.AttrString("importpath"); importPath == "" {
39 return []resolve.ImportSpec{}
40 } else {
41 return []resolve.ImportSpec{{
42 Lang: goName,
43 Imp: importPath,
44 }}
45 }
46 }
47
48 func (*goLang) Embeds(r *rule.Rule, from label.Label) []label.Label {
49 embedStrings := r.AttrStrings("embed")
50 if isGoProtoLibrary(r.Kind()) {
51 embedStrings = append(embedStrings, r.AttrString("proto"))
52 }
53 embedLabels := make([]label.Label, 0, len(embedStrings))
54 for _, s := range embedStrings {
55 l, err := label.Parse(s)
56 if err != nil {
57 continue
58 }
59 l = l.Abs(from.Repo, from.Pkg)
60 embedLabels = append(embedLabels, l)
61 }
62 return embedLabels
63 }
64
65 func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, importsRaw interface{}, from label.Label) {
66 if importsRaw == nil {
67
68 return
69 }
70 imports := importsRaw.(rule.PlatformStrings)
71 r.DelAttr("deps")
72 var resolve func(*config.Config, *resolve.RuleIndex, *repo.RemoteCache, string, label.Label) (label.Label, error)
73 switch r.Kind() {
74 case "go_proto_library":
75 resolve = resolveProto
76 default:
77 resolve = ResolveGo
78 }
79 deps, errs := imports.Map(func(imp string) (string, error) {
80 l, err := resolve(c, ix, rc, imp, from)
81 if err == errSkipImport {
82 return "", nil
83 } else if err != nil {
84 return "", err
85 }
86 for _, embed := range gl.Embeds(r, from) {
87 if embed.Equal(l) {
88 return "", nil
89 }
90 }
91 l = l.Rel(from.Repo, from.Pkg)
92 return l.String(), nil
93 })
94 for _, err := range errs {
95 log.Print(err)
96 }
97 if !deps.IsEmpty() {
98 if r.Kind() == "go_proto_library" {
99
100
101
102 r.SetAttr("deps", deps.Flat())
103 } else {
104 r.SetAttr("deps", deps)
105 }
106 }
107 }
108
109 var (
110 errSkipImport = errors.New("std or self import")
111 errNotFound = errors.New("rule not found")
112 )
113
114
115
116
117
118
119
120 func ResolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, imp string, from label.Label) (label.Label, error) {
121 gc := getGoConfig(c)
122 if build.IsLocalImport(imp) {
123 cleanRel := path.Clean(path.Join(from.Pkg, imp))
124 if build.IsLocalImport(cleanRel) {
125 return label.NoLabel, fmt.Errorf("relative import path %q from %q points outside of repository", imp, from.Pkg)
126 }
127 imp = path.Join(gc.prefix, cleanRel)
128 }
129
130 if IsStandard(imp) {
131 return label.NoLabel, errSkipImport
132 }
133
134 if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "go", Imp: imp}, "go"); ok {
135 return l, nil
136 }
137
138 if l, err := resolveWithIndexGo(c, ix, imp, from); err == nil || err == errSkipImport {
139 return l, err
140 } else if err != errNotFound {
141 return label.NoLabel, err
142 }
143
144
145
146
147
148 if !c.Bzlmod {
149 if pathtools.HasPrefix(imp, "github.com/bazelbuild/rules_go") {
150 pkg := pathtools.TrimPrefix(imp, "github.com/bazelbuild/rules_go")
151 return label.New("io_bazel_rules_go", pkg, "go_default_library"), nil
152 } else if pathtools.HasPrefix(imp, "github.com/bazelbuild/bazel-gazelle") {
153 pkg := pathtools.TrimPrefix(imp, "github.com/bazelbuild/bazel-gazelle")
154 return label.New("bazel_gazelle", pkg, "go_default_library"), nil
155 }
156 }
157
158 if !c.IndexLibraries {
159
160
161 if pathtools.HasPrefix(imp, gc.prefix) {
162 pkg := path.Join(gc.prefixRel, pathtools.TrimPrefix(imp, gc.prefix))
163 libName := libNameByConvention(gc.goNamingConvention, imp, "")
164 return label.New("", pkg, libName), nil
165 }
166 }
167
168 if gc.depMode == vendorMode {
169 return resolveVendored(gc, imp)
170 }
171 var resolveFn func(string) (string, string, error)
172 if gc.depMode == staticMode {
173 resolveFn = rc.RootStatic
174 } else if gc.moduleMode || pathWithoutSemver(imp) != "" {
175 resolveFn = rc.Mod
176 } else {
177 resolveFn = rc.Root
178 }
179 return resolveToExternalLabel(c, resolveFn, imp)
180 }
181
182
183 func IsStandard(imp string) bool {
184 return stdPackages[imp]
185 }
186
187 func resolveWithIndexGo(c *config.Config, ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) {
188 matches := ix.FindRulesByImportWithConfig(c, resolve.ImportSpec{Lang: "go", Imp: imp}, "go")
189 var bestMatch resolve.FindResult
190 var bestMatchIsVendored bool
191 var bestMatchVendorRoot string
192 var bestMatchEmbedsProtos bool
193 var matchError error
194 goRepositoryMode := getGoConfig(c).goRepositoryMode
195
196 for _, m := range matches {
197
198
199
200
201
202
203
204
205
206 isVendored := false
207 vendorRoot := ""
208 parts := strings.Split(m.Label.Pkg, "/")
209 for i := len(parts) - 1; i >= 0; i-- {
210 if parts[i] == "vendor" {
211 isVendored = true
212 vendorRoot = strings.Join(parts[:i], "/")
213 break
214 }
215 }
216 if isVendored && !label.New(m.Label.Repo, vendorRoot, "").Contains(from) {
217
218 continue
219 }
220
221 embedsProtos := false
222 for _, embed := range m.Embeds {
223 if strings.HasSuffix(embed.Name, goProtoSuffix) {
224 embedsProtos = true
225 }
226 }
227
228 if bestMatch.Label.Equal(label.NoLabel) ||
229 (isVendored && (!bestMatchIsVendored || len(vendorRoot) > len(bestMatchVendorRoot))) ||
230 (goRepositoryMode && !bestMatchEmbedsProtos && embedsProtos) {
231
232 bestMatch = m
233 bestMatchIsVendored = isVendored
234 bestMatchVendorRoot = vendorRoot
235 bestMatchEmbedsProtos = embedsProtos
236 matchError = nil
237 } else if (!isVendored && bestMatchIsVendored) ||
238 (isVendored && len(vendorRoot) < len(bestMatchVendorRoot)) ||
239 (goRepositoryMode && bestMatchEmbedsProtos && !embedsProtos) {
240
241 } else {
242
243
244 matchError = fmt.Errorf("rule %s imports %q which matches multiple rules: %s and %s. # gazelle:resolve may be used to disambiguate", from, imp, bestMatch.Label, m.Label)
245 }
246 }
247 if matchError != nil {
248 return label.NoLabel, matchError
249 }
250 if bestMatch.Label.Equal(label.NoLabel) {
251 return label.NoLabel, errNotFound
252 }
253 if bestMatch.IsSelfImport(from) {
254 return label.NoLabel, errSkipImport
255 }
256 return bestMatch.Label, nil
257 }
258
259 func resolveToExternalLabel(c *config.Config, resolveFn func(string) (string, string, error), imp string) (label.Label, error) {
260 prefix, repo, err := resolveFn(imp)
261 if err != nil {
262 return label.NoLabel, err
263 } else if prefix == "" && repo == "" {
264 return label.NoLabel, errSkipImport
265 }
266
267 var pkg string
268 if pathtools.HasPrefix(imp, prefix) {
269 pkg = pathtools.TrimPrefix(imp, prefix)
270 } else if impWithoutSemver := pathWithoutSemver(imp); pathtools.HasPrefix(impWithoutSemver, prefix) {
271
272
273 pkg = pathtools.TrimPrefix(impWithoutSemver, prefix)
274 }
275
276
277
278
279
280
281
282
283 gc := getGoConfig(c)
284 nc := gc.repoNamingConvention[repo]
285 if nc == unknownNamingConvention {
286 if gc.goNamingConventionExternal != unknownNamingConvention {
287 nc = gc.goNamingConventionExternal
288 } else {
289 nc = goDefaultLibraryNamingConvention
290 }
291 } else if nc == importAliasNamingConvention {
292 if gc.goNamingConventionExternal != unknownNamingConvention {
293 nc = gc.goNamingConventionExternal
294 } else {
295 nc = gc.goNamingConvention
296 }
297 }
298
299 name := libNameByConvention(nc, imp, "")
300 return label.New(repo, pkg, name), nil
301 }
302
303 func resolveVendored(gc *goConfig, imp string) (label.Label, error) {
304 name := libNameByConvention(gc.goNamingConvention, imp, "")
305 return label.New("", path.Join("vendor", imp), name), nil
306 }
307
308 func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, imp string, from label.Label) (label.Label, error) {
309 if wellKnownProtos[imp] {
310 return label.NoLabel, errSkipImport
311 }
312
313 if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "go"); ok {
314 return l, nil
315 }
316
317 if l, err := resolveWithIndexProto(c, ix, imp, from); err == nil || err == errSkipImport {
318 return l, err
319 } else if err != errNotFound {
320 return label.NoLabel, err
321 }
322
323
324
325
326
327 rel := path.Dir(imp)
328 if rel == "." {
329 rel = ""
330 }
331 if from.Pkg == "vendor" || strings.HasPrefix(from.Pkg, "vendor/") {
332 rel = path.Join("vendor", rel)
333 }
334 libName := libNameByConvention(getGoConfig(c).goNamingConvention, imp, "")
335 return label.New("", rel, libName), nil
336 }
337
338
339
340
341
342 var wellKnownProtos = map[string]bool{
343 "google/protobuf/any.proto": true,
344 "google/protobuf/api.proto": true,
345 "google/protobuf/compiler/plugin.proto": true,
346 "google/protobuf/descriptor.proto": true,
347 "google/protobuf/duration.proto": true,
348 "google/protobuf/empty.proto": true,
349 "google/protobuf/field_mask.proto": true,
350 "google/protobuf/source_context.proto": true,
351 "google/protobuf/struct.proto": true,
352 "google/protobuf/timestamp.proto": true,
353 "google/protobuf/type.proto": true,
354 "google/protobuf/wrappers.proto": true,
355 }
356
357 func resolveWithIndexProto(c *config.Config, ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) {
358 matches := ix.FindRulesByImportWithConfig(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "go")
359 if len(matches) == 0 {
360 return label.NoLabel, errNotFound
361 }
362 if len(matches) > 1 {
363 return label.NoLabel, fmt.Errorf("multiple rules (%s and %s) may be imported with %q from %s", matches[0].Label, matches[1].Label, imp, from)
364 }
365 if matches[0].IsSelfImport(from) {
366 return label.NoLabel, errSkipImport
367 }
368 return matches[0].Label, nil
369 }
370
371 func isGoLibrary(kind string) bool {
372 return kind == "go_library" || isGoProtoLibrary(kind)
373 }
374
375 func isGoProtoLibrary(kind string) bool {
376 return kind == "go_proto_library" || kind == "go_grpc_library"
377 }
378
379
380
381
382 func isExtraLibrary(r *rule.Rule) bool {
383 if !strings.HasSuffix(r.Name(), "_gen") {
384 return false
385 }
386 switch r.AttrString("importpath") {
387 case "github.com/golang/protobuf/descriptor",
388 "github.com/golang/protobuf/protoc-gen-go/generator",
389 "github.com/golang/protobuf/ptypes":
390 return true
391 default:
392 return false
393 }
394 }
395
View as plain text