label.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /* Copyright 2016 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 label
  13. import (
  14. "fmt"
  15. "log"
  16. "path"
  17. "regexp"
  18. "strings"
  19. "github.com/bazelbuild/bazel-gazelle/internal/pathtools"
  20. )
  21. // A Label represents a label of a build target in Bazel.
  22. type Label struct {
  23. Repo, Pkg, Name string
  24. Relative bool
  25. }
  26. func New(repo, pkg, name string) Label {
  27. return Label{Repo: repo, Pkg: pkg, Name: name}
  28. }
  29. // NoLabel is the nil value of Label. It is not a valid label and may be
  30. // returned when an error occurs.
  31. var NoLabel = Label{}
  32. var (
  33. labelRepoRegexp = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
  34. labelPkgRegexp = regexp.MustCompile(`^[A-Za-z0-9/._-]*$`)
  35. labelNameRegexp = regexp.MustCompile(`^[A-Za-z0-9_/.+=,@~-]*$`)
  36. )
  37. // Parse reads a label from a string.
  38. // See https://docs.bazel.build/versions/master/build-ref.html#lexi.
  39. func Parse(s string) (Label, error) {
  40. origStr := s
  41. relative := true
  42. var repo string
  43. if strings.HasPrefix(s, "@") {
  44. relative = false
  45. endRepo := strings.Index(s, "//")
  46. if endRepo < 0 {
  47. return NoLabel, fmt.Errorf("label parse error: repository does not end with '//': %q", origStr)
  48. }
  49. repo = s[len("@"):endRepo]
  50. if !labelRepoRegexp.MatchString(repo) {
  51. return NoLabel, fmt.Errorf("label parse error: repository has invalid characters: %q", origStr)
  52. }
  53. s = s[endRepo:]
  54. }
  55. var pkg string
  56. if strings.HasPrefix(s, "//") {
  57. relative = false
  58. endPkg := strings.Index(s, ":")
  59. if endPkg < 0 {
  60. pkg = s[len("//"):]
  61. s = ""
  62. } else {
  63. pkg = s[len("//"):endPkg]
  64. s = s[endPkg:]
  65. }
  66. if !labelPkgRegexp.MatchString(pkg) {
  67. return NoLabel, fmt.Errorf("label parse error: package has invalid characters: %q", origStr)
  68. }
  69. }
  70. if s == ":" {
  71. return NoLabel, fmt.Errorf("label parse error: empty name: %q", origStr)
  72. }
  73. name := strings.TrimPrefix(s, ":")
  74. if !labelNameRegexp.MatchString(name) {
  75. return NoLabel, fmt.Errorf("label parse error: name has invalid characters: %q", origStr)
  76. }
  77. if pkg == "" && name == "" {
  78. return NoLabel, fmt.Errorf("label parse error: empty package and name: %q", origStr)
  79. }
  80. if name == "" {
  81. name = path.Base(pkg)
  82. }
  83. return Label{
  84. Repo: repo,
  85. Pkg: pkg,
  86. Name: name,
  87. Relative: relative,
  88. }, nil
  89. }
  90. func (l Label) String() string {
  91. if l.Relative {
  92. return fmt.Sprintf(":%s", l.Name)
  93. }
  94. var repo string
  95. if l.Repo != "" {
  96. repo = fmt.Sprintf("@%s", l.Repo)
  97. }
  98. if path.Base(l.Pkg) == l.Name {
  99. return fmt.Sprintf("%s//%s", repo, l.Pkg)
  100. }
  101. return fmt.Sprintf("%s//%s:%s", repo, l.Pkg, l.Name)
  102. }
  103. func (l Label) Abs(repo, pkg string) Label {
  104. if !l.Relative {
  105. return l
  106. }
  107. return Label{Repo: repo, Pkg: pkg, Name: l.Name}
  108. }
  109. func (l Label) Rel(repo, pkg string) Label {
  110. if l.Relative || l.Repo != repo {
  111. return l
  112. }
  113. if l.Pkg == pkg {
  114. return Label{Name: l.Name, Relative: true}
  115. }
  116. return Label{Pkg: l.Pkg, Name: l.Name}
  117. }
  118. func (l Label) Equal(other Label) bool {
  119. return l.Repo == other.Repo &&
  120. l.Pkg == other.Pkg &&
  121. l.Name == other.Name &&
  122. l.Relative == other.Relative
  123. }
  124. // Contains returns whether other is contained by the package of l or a
  125. // sub-package. Neither label may be relative.
  126. func (l Label) Contains(other Label) bool {
  127. if l.Relative {
  128. log.Panicf("l must not be relative: %s", l)
  129. }
  130. if other.Relative {
  131. log.Panicf("other must not be relative: %s", other)
  132. }
  133. result := l.Repo == other.Repo && pathtools.HasPrefix(other.Pkg, l.Pkg)
  134. return result
  135. }
  136. // ImportPathToBazelRepoName converts a Go import path into a bazel repo name
  137. // following the guidelines in http://bazel.io/docs/be/functions.html#workspace
  138. func ImportPathToBazelRepoName(importpath string) string {
  139. importpath = strings.ToLower(importpath)
  140. components := strings.Split(importpath, "/")
  141. labels := strings.Split(components[0], ".")
  142. var reversed []string
  143. for i := range labels {
  144. l := labels[len(labels)-i-1]
  145. reversed = append(reversed, l)
  146. }
  147. repo := strings.Join(append(reversed, components[1:]...), "_")
  148. return strings.NewReplacer("-", "_", ".", "_").Replace(repo)
  149. }