1
15
16 package golang
17
18 import (
19 "fmt"
20 "log"
21 "path"
22 "regexp"
23 "sort"
24 "strings"
25
26 "github.com/bazelbuild/bazel-gazelle/config"
27 "github.com/bazelbuild/bazel-gazelle/language/proto"
28 "github.com/bazelbuild/bazel-gazelle/pathtools"
29 "github.com/bazelbuild/bazel-gazelle/rule"
30 )
31
32
33
34 type goPackage struct {
35 name, dir, rel string
36 library, binary, test goTarget
37 tests []goTarget
38 proto protoTarget
39 hasTestdata bool
40 hasMainFunction bool
41 importPath string
42 }
43
44
45
46 type goTarget struct {
47 sources, embedSrcs, imports, cppopts, copts, cxxopts, clinkopts platformStringsBuilder
48 cgo, hasInternalTest bool
49 }
50
51
52 type protoTarget struct {
53 name string
54 sources platformStringsBuilder
55 imports platformStringsBuilder
56 hasServices bool
57 }
58
59
60
61
62
63 type platformStringsBuilder struct {
64 strs map[string]platformStringInfo
65 }
66
67
68
69 type platformStringInfo struct {
70 set platformStringSet
71 osConstraints map[string]bool
72 archConstraints map[string]bool
73 platformConstraints map[rule.PlatformConstraint]bool
74 }
75
76 type platformStringSet int
77
78 const (
79 genericSet platformStringSet = iota
80 osSet
81 archSet
82 platformSet
83 )
84
85
86 var pkgVersionRe = regexp.MustCompile("^v[0-9]+$")
87
88
89
90
91
92
93
94
95
96
97 func (pkg *goPackage) addFile(c *config.Config, er *embedResolver, info fileInfo, cgo bool) error {
98 switch {
99 case info.ext == unknownExt || !cgo && (info.ext == cExt || info.ext == csExt):
100 return nil
101 case info.ext == protoExt:
102 if pcMode := getProtoMode(c); pcMode == proto.LegacyMode {
103
104
105
106 pkg.proto.addFile(info)
107 }
108 case info.isTest:
109 if info.isCgo {
110 return fmt.Errorf("%s: use of cgo in test not supported", info.path)
111 }
112 if getGoConfig(c).testMode == fileTestMode || len(pkg.tests) == 0 {
113 pkg.tests = append(pkg.tests, goTarget{})
114 }
115
116
117
118 test := &pkg.tests[len(pkg.tests)-1]
119 test.addFile(c, er, info)
120 if !info.isExternalTest {
121 test.hasInternalTest = true
122 }
123 default:
124 pkg.hasMainFunction = pkg.hasMainFunction || info.hasMainFunction
125 pkg.library.addFile(c, er, info)
126 }
127
128 return nil
129 }
130
131
132 func (pkg *goPackage) isCommand() bool {
133 return pkg.name == "main" && pkg.hasMainFunction
134 }
135
136
137
138
139 func (pkg *goPackage) isBuildable(c *config.Config) bool {
140 return pkg.firstGoFile() != "" || !pkg.proto.sources.isEmpty()
141 }
142
143
144
145 func (pkg *goPackage) firstGoFile() string {
146 goSrcs := []platformStringsBuilder{
147 pkg.library.sources,
148 pkg.binary.sources,
149 }
150 for _, test := range pkg.tests {
151 goSrcs = append(goSrcs, test.sources)
152 }
153
154 for _, sb := range goSrcs {
155 if sb.strs != nil {
156 for s := range sb.strs {
157 if strings.HasSuffix(s, ".go") {
158 return s
159 }
160 }
161 }
162 }
163 return ""
164 }
165
166 func (pkg *goPackage) haveCgo() bool {
167 if pkg.library.cgo || pkg.binary.cgo {
168 return true
169 }
170 for _, t := range pkg.tests {
171 if t.cgo {
172 return true
173 }
174 }
175 return false
176 }
177
178 func (pkg *goPackage) inferImportPath(c *config.Config) error {
179 if pkg.importPath != "" {
180 log.Panic("importPath already set")
181 }
182 gc := getGoConfig(c)
183 if !gc.prefixSet {
184 return fmt.Errorf("%s: go prefix is not set, so importpath can't be determined for rules. Set a prefix with a '# gazelle:prefix' comment or with -go_prefix on the command line", pkg.dir)
185 }
186 pkg.importPath = InferImportPath(c, pkg.rel)
187 return nil
188 }
189
190
191
192 func libNameFromImportPath(dir string) string {
193 i := strings.LastIndexAny(dir, "/\\")
194 if i < 0 {
195 return dir
196 }
197 name := dir[i+1:]
198 if pkgVersionRe.MatchString(name) {
199 dir := dir[:i]
200 i = strings.LastIndexAny(dir, "/\\")
201 if i >= 0 {
202 name = dir[i+1:]
203 }
204 }
205 return strings.ReplaceAll(name, ".", "_")
206 }
207
208
209
210 func libNameByConvention(nc namingConvention, imp string, pkgName string) string {
211 if nc == goDefaultLibraryNamingConvention {
212 return defaultLibName
213 }
214 name := libNameFromImportPath(imp)
215 isCommand := pkgName == "main"
216 if name == "" {
217 if isCommand {
218 name = "lib"
219 } else {
220 name = pkgName
221 }
222 } else if isCommand {
223 name += "_lib"
224 }
225 return name
226 }
227
228
229
230 func testNameByConvention(nc namingConvention, imp string) string {
231 if nc == goDefaultLibraryNamingConvention {
232 return defaultTestName
233 }
234 libName := libNameFromImportPath(imp)
235 if libName == "" {
236 libName = "lib"
237 }
238 return libName + "_test"
239 }
240
241
242
243 func testNameFromSingleSource(src string) string {
244 if i := strings.LastIndexByte(src, '.'); i >= 0 {
245 src = src[0:i]
246 }
247 libName := libNameFromImportPath(src)
248 if libName == "" {
249 return ""
250 }
251 if strings.HasSuffix(libName, "_test") {
252 return libName
253 }
254 return libName + "_test"
255 }
256
257
258 func binName(rel, prefix, repoRoot string) string {
259 return pathtools.RelBaseName(rel, prefix, repoRoot)
260 }
261
262 func InferImportPath(c *config.Config, rel string) string {
263 gc := getGoConfig(c)
264 if rel == gc.prefixRel {
265 return gc.prefix
266 } else {
267 fromPrefixRel := strings.TrimPrefix(rel, gc.prefixRel+"/")
268 return path.Join(gc.prefix, fromPrefixRel)
269 }
270 }
271
272 func goProtoPackageName(pkg proto.Package) string {
273 if value, ok := pkg.Options["go_package"]; ok {
274 if strings.LastIndexByte(value, '/') == -1 {
275 return value
276 } else {
277 if i := strings.LastIndexByte(value, ';'); i != -1 {
278 return value[i+1:]
279 } else {
280 return path.Base(value)
281 }
282 }
283 }
284 return strings.Replace(pkg.Name, ".", "_", -1)
285 }
286
287 func goProtoImportPath(c *config.Config, pkg proto.Package, rel string) string {
288 if value, ok := pkg.Options["go_package"]; ok {
289 if strings.LastIndexByte(value, '/') == -1 {
290 return InferImportPath(c, rel)
291 } else if i := strings.LastIndexByte(value, ';'); i != -1 {
292 return value[:i]
293 } else {
294 return value
295 }
296 }
297 return InferImportPath(c, rel)
298 }
299
300 func (t *goTarget) addFile(c *config.Config, er *embedResolver, info fileInfo) {
301 t.cgo = t.cgo || info.isCgo
302 add := getPlatformStringsAddFunction(c, info, nil)
303 add(&t.sources, info.name)
304 add(&t.imports, info.imports...)
305 if er != nil {
306 for _, embed := range info.embeds {
307 embedSrcs, err := er.resolve(embed)
308 if err != nil {
309 log.Print(err)
310 continue
311 }
312 add(&t.embedSrcs, embedSrcs...)
313 }
314 }
315 for _, cppopts := range info.cppopts {
316 optAdd := add
317 if !cppopts.empty() {
318 optAdd = getPlatformStringsAddFunction(c, info, cppopts)
319 }
320 optAdd(&t.cppopts, cppopts.opts)
321 }
322 for _, copts := range info.copts {
323 optAdd := add
324 if !copts.empty() {
325 optAdd = getPlatformStringsAddFunction(c, info, copts)
326 }
327 optAdd(&t.copts, copts.opts)
328 }
329 for _, cxxopts := range info.cxxopts {
330 optAdd := add
331 if !cxxopts.empty() {
332 optAdd = getPlatformStringsAddFunction(c, info, cxxopts)
333 }
334 optAdd(&t.cxxopts, cxxopts.opts)
335 }
336 for _, clinkopts := range info.clinkopts {
337 optAdd := add
338 if !clinkopts.empty() {
339 optAdd = getPlatformStringsAddFunction(c, info, clinkopts)
340 }
341 optAdd(&t.clinkopts, clinkopts.opts)
342 }
343 }
344
345 func protoTargetFromProtoPackage(name string, pkg proto.Package) protoTarget {
346 target := protoTarget{name: name}
347 for f := range pkg.Files {
348 target.sources.addGenericString(f)
349 }
350 for i := range pkg.Imports {
351 target.imports.addGenericString(i)
352 }
353 target.hasServices = pkg.HasServices
354 return target
355 }
356
357 func (t *protoTarget) addFile(info fileInfo) {
358 t.sources.addGenericString(info.name)
359 for _, imp := range info.imports {
360 t.imports.addGenericString(imp)
361 }
362 t.hasServices = t.hasServices || info.hasServices
363 }
364
365
366
367
368 func getPlatformStringsAddFunction(c *config.Config, info fileInfo, cgoTags *cgoTagsAndOpts) func(sb *platformStringsBuilder, ss ...string) {
369 isOSSpecific, isArchSpecific := isOSArchSpecific(info, cgoTags)
370 v := getGoConfig(c).rulesGoVersion
371 constraintPrefix := "@" + getGoConfig(c).rulesGoRepoName + "//go/platform:"
372
373 switch {
374 case !isOSSpecific && !isArchSpecific:
375 if checkConstraints(c, "", "", info.goos, info.goarch, info.tags, cgoTags) {
376 return func(sb *platformStringsBuilder, ss ...string) {
377 for _, s := range ss {
378 sb.addGenericString(s)
379 }
380 }
381 }
382
383 case isOSSpecific && !isArchSpecific:
384 var osMatch []string
385 for _, os := range rule.KnownOSs {
386 if rulesGoSupportsOS(v, os) &&
387 checkConstraints(c, os, "", info.goos, info.goarch, info.tags, cgoTags) {
388 osMatch = append(osMatch, os)
389 }
390 }
391 if len(osMatch) > 0 {
392 return func(sb *platformStringsBuilder, ss ...string) {
393 for _, s := range ss {
394 sb.addOSString(s, osMatch, constraintPrefix)
395 }
396 }
397 }
398
399 case !isOSSpecific && isArchSpecific:
400 var archMatch []string
401 for _, arch := range rule.KnownArchs {
402 if rulesGoSupportsArch(v, arch) &&
403 checkConstraints(c, "", arch, info.goos, info.goarch, info.tags, cgoTags) {
404 archMatch = append(archMatch, arch)
405 }
406 }
407 if len(archMatch) > 0 {
408 return func(sb *platformStringsBuilder, ss ...string) {
409 for _, s := range ss {
410 sb.addArchString(s, archMatch, constraintPrefix)
411 }
412 }
413 }
414
415 default:
416 var platformMatch []rule.Platform
417 for _, platform := range rule.KnownPlatforms {
418 if rulesGoSupportsPlatform(v, platform) &&
419 checkConstraints(c, platform.OS, platform.Arch, info.goos, info.goarch, info.tags, cgoTags) {
420 platformMatch = append(platformMatch, platform)
421 }
422 }
423 if len(platformMatch) > 0 {
424 return func(sb *platformStringsBuilder, ss ...string) {
425 for _, s := range ss {
426 sb.addPlatformString(s, platformMatch, constraintPrefix)
427 }
428 }
429 }
430 }
431
432 return func(_ *platformStringsBuilder, _ ...string) {}
433 }
434
435 func (sb *platformStringsBuilder) isEmpty() bool {
436 return sb.strs == nil
437 }
438
439 func (sb *platformStringsBuilder) hasGo() bool {
440 for s := range sb.strs {
441 if strings.HasSuffix(s, ".go") {
442 return true
443 }
444 }
445 return false
446 }
447
448 func (sb *platformStringsBuilder) addGenericString(s string) {
449 if sb.strs == nil {
450 sb.strs = make(map[string]platformStringInfo)
451 }
452 sb.strs[s] = platformStringInfo{set: genericSet}
453 }
454
455 func (sb *platformStringsBuilder) addOSString(s string, oss []string, constraintPrefix string) {
456 if sb.strs == nil {
457 sb.strs = make(map[string]platformStringInfo)
458 }
459 si, ok := sb.strs[s]
460 if !ok {
461 si.set = osSet
462 si.osConstraints = make(map[string]bool)
463 }
464 switch si.set {
465 case genericSet:
466 return
467 case osSet:
468 for _, os := range oss {
469 si.osConstraints[constraintPrefix+os] = true
470 }
471 default:
472 si.convertToPlatforms(constraintPrefix)
473 for _, os := range oss {
474 for _, arch := range rule.KnownOSArchs[os] {
475 si.platformConstraints[rule.PlatformConstraint{
476 Platform: rule.Platform{OS: os, Arch: arch},
477 ConstraintPrefix: constraintPrefix,
478 }] = true
479 }
480 }
481 }
482 sb.strs[s] = si
483 }
484
485 func (sb *platformStringsBuilder) addArchString(s string, archs []string, constraintPrefix string) {
486 if sb.strs == nil {
487 sb.strs = make(map[string]platformStringInfo)
488 }
489 si, ok := sb.strs[s]
490 if !ok {
491 si.set = archSet
492 si.archConstraints = make(map[string]bool)
493 }
494 switch si.set {
495 case genericSet:
496 return
497 case archSet:
498 for _, arch := range archs {
499 si.archConstraints[constraintPrefix+arch] = true
500 }
501 default:
502 si.convertToPlatforms(constraintPrefix)
503 for _, arch := range archs {
504 for _, os := range rule.KnownArchOSs[arch] {
505 si.platformConstraints[rule.PlatformConstraint{
506 Platform: rule.Platform{OS: os, Arch: arch},
507 ConstraintPrefix: constraintPrefix,
508 }] = true
509 }
510 }
511 }
512 sb.strs[s] = si
513 }
514
515 func (sb *platformStringsBuilder) addPlatformString(s string, platforms []rule.Platform, constraintPrefix string) {
516 if sb.strs == nil {
517 sb.strs = make(map[string]platformStringInfo)
518 }
519 si, ok := sb.strs[s]
520 if !ok {
521 si.set = platformSet
522 si.platformConstraints = make(map[rule.PlatformConstraint]bool)
523 }
524 switch si.set {
525 case genericSet:
526 return
527 default:
528 si.convertToPlatforms(constraintPrefix)
529 for _, p := range platforms {
530 pConstraint := rule.PlatformConstraint{Platform: rule.Platform{OS: p.OS, Arch: p.Arch}, ConstraintPrefix: constraintPrefix}
531 si.platformConstraints[pConstraint] = true
532 }
533 }
534 sb.strs[s] = si
535 }
536
537 func (sb *platformStringsBuilder) build() rule.PlatformStrings {
538 var ps rule.PlatformStrings
539 for s, si := range sb.strs {
540 switch si.set {
541 case genericSet:
542 ps.Generic = append(ps.Generic, s)
543 case osSet:
544 if ps.OS == nil {
545 ps.OS = make(map[string][]string)
546 }
547 for os := range si.osConstraints {
548 ps.OS[os] = append(ps.OS[os], s)
549 }
550 case archSet:
551 if ps.Arch == nil {
552 ps.Arch = make(map[string][]string)
553 }
554 for arch := range si.archConstraints {
555 ps.Arch[arch] = append(ps.Arch[arch], s)
556 }
557 case platformSet:
558 if ps.Platform == nil {
559 ps.Platform = make(map[rule.PlatformConstraint][]string)
560 }
561 for p := range si.platformConstraints {
562 ps.Platform[p] = append(ps.Platform[p], s)
563 }
564 }
565 }
566 sort.Strings(ps.Generic)
567 if ps.OS != nil {
568 for _, ss := range ps.OS {
569 sort.Strings(ss)
570 }
571 }
572 if ps.Arch != nil {
573 for _, ss := range ps.Arch {
574 sort.Strings(ss)
575 }
576 }
577 if ps.Platform != nil {
578 for _, ss := range ps.Platform {
579 sort.Strings(ss)
580 }
581 }
582 return ps
583 }
584
585 func (sb *platformStringsBuilder) buildFlat() []string {
586 strs := make([]string, 0, len(sb.strs))
587 for s := range sb.strs {
588 strs = append(strs, s)
589 }
590 sort.Strings(strs)
591 return strs
592 }
593
594 func (si *platformStringInfo) convertToPlatforms(constraintPrefix string) {
595 switch si.set {
596 case genericSet:
597 log.Panic("cannot convert generic string to platformConstraints")
598 case platformSet:
599 return
600 case osSet:
601 si.set = platformSet
602 si.platformConstraints = make(map[rule.PlatformConstraint]bool)
603 for osConstraint := range si.osConstraints {
604 os := strings.TrimPrefix(osConstraint, constraintPrefix)
605 for _, arch := range rule.KnownOSArchs[os] {
606 si.platformConstraints[rule.PlatformConstraint{
607 Platform: rule.Platform{OS: os, Arch: arch},
608 ConstraintPrefix: constraintPrefix,
609 }] = true
610 }
611 }
612 si.osConstraints = nil
613 case archSet:
614 si.set = platformSet
615 si.platformConstraints = make(map[rule.PlatformConstraint]bool)
616 for archConstraint := range si.archConstraints {
617 arch := strings.TrimPrefix(archConstraint, constraintPrefix)
618 for _, os := range rule.KnownArchOSs[arch] {
619 si.platformConstraints[rule.PlatformConstraint{
620 Platform: rule.Platform{OS: os, Arch: arch},
621 ConstraintPrefix: constraintPrefix,
622 }] = true
623 }
624 }
625 si.archConstraints = nil
626 }
627 }
628
629 var semverRex = regexp.MustCompile(`^.*?(/v\d+)(?:/.*)?$`)
630
631
632
633
634
635 func pathWithoutSemver(path string) string {
636 m := semverRex.FindStringSubmatchIndex(path)
637 if m == nil {
638 return ""
639 }
640 v := path[m[2]+2 : m[3]]
641 if v[0] == '0' || v == "1" {
642 return ""
643 }
644 return path[:m[2]] + path[m[3]:]
645 }
646
View as plain text