label.go 5.7 KB

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