github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/label/label.go (about) 1 /* Copyright 2016 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 // Package label provides utilities for parsing and manipulating 17 // Bazel labels. See 18 // https://docs.bazel.build/versions/master/build-ref.html#labels 19 // for more information. 20 package label 21 22 import ( 23 "fmt" 24 "log" 25 "path" 26 "regexp" 27 "strings" 28 29 "github.com/bazelbuild/bazel-gazelle/pathtools" 30 bzl "github.com/bazelbuild/buildtools/build" 31 ) 32 33 // A Label represents a label of a build target in Bazel. Labels have three 34 // parts: a repository name, a package name, and a target name, formatted 35 // as @repo//pkg:target. 36 type Label struct { 37 // Repo is the repository name. If omitted, the label refers to a target 38 // in the current repository. 39 Repo string 40 41 // Pkg is the package name, which is usually the directory that contains 42 // the target. If both Repo and Pkg are omitted, the label is relative. 43 Pkg string 44 45 // Name is the name of the target the label refers to. Name must not be empty. 46 // Note that the name may be omitted from a label string if it is equal to 47 // the last component of the package name ("//x" is equivalent to "//x:x"), 48 // but in either case, Name should be set here. 49 Name string 50 51 // Relative indicates whether the label refers to a target in the current 52 // package. Relative is true if and only if Repo and Pkg are both omitted. 53 Relative bool 54 } 55 56 // New constructs a new label from components. 57 func New(repo, pkg, name string) Label { 58 return Label{Repo: repo, Pkg: pkg, Name: name} 59 } 60 61 // NoLabel is the zero value of Label. It is not a valid label and may be 62 // returned when an error occurs. 63 var NoLabel = Label{} 64 65 var ( 66 // This was taken from https://github.com/bazelbuild/bazel/blob/71fb1e4188b01e582a308cfe4bcbf1c730eded1b/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java#L159C1-L164 67 labelRepoRegexp = regexp.MustCompile(`^@$|^[A-Za-z0-9_.-][A-Za-z0-9_.~-]*$`) 68 // This was taken from https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/cmdline/LabelValidator.java 69 // Package names may contain all 7-bit ASCII characters except: 70 // 0-31 (control characters) 71 // 58 ':' (colon) - target name separator 72 // 92 '\' (backslash) - directory separator (on Windows); may be allowed in the future 73 // 127 (delete) 74 // Target names may contain the same characters 75 labelPkgRegexp = regexp.MustCompile(`^[\x20-\x39\x3B-\x5B\x5D-\x7E]*$`) 76 labelNameRegexp = labelPkgRegexp 77 ) 78 79 // Parse reads a label from a string. 80 // See https://docs.bazel.build/versions/master/build-ref.html#lexi. 81 func Parse(s string) (Label, error) { 82 origStr := s 83 84 relative := true 85 var repo string 86 // if target name begins @@ drop the first @ 87 if strings.HasPrefix(s, "@@") { 88 s = s[len("@"):] 89 } 90 if strings.HasPrefix(s, "@") { 91 relative = false 92 endRepo := strings.Index(s, "//") 93 if endRepo > len("@") { 94 repo = s[len("@"):endRepo] 95 s = s[endRepo:] 96 // If the label begins with "@//...", set repo = "@" 97 // to remain distinct from "//...", where repo = "" 98 } else if endRepo == len("@") { 99 repo = s[:len("@")] 100 s = s[len("@"):] 101 } else { 102 repo = s[len("@"):] 103 s = "//:" + repo 104 } 105 if !labelRepoRegexp.MatchString(repo) { 106 return NoLabel, fmt.Errorf("label parse error: repository has invalid characters: %q", origStr) 107 } 108 } 109 110 var pkg string 111 if strings.HasPrefix(s, "//") { 112 relative = false 113 endPkg := strings.Index(s, ":") 114 if endPkg < 0 { 115 pkg = s[len("//"):] 116 s = "" 117 } else { 118 pkg = s[len("//"):endPkg] 119 s = s[endPkg:] 120 } 121 if !labelPkgRegexp.MatchString(pkg) { 122 return NoLabel, fmt.Errorf("label parse error: package has invalid characters: %q", origStr) 123 } 124 } 125 126 if s == ":" { 127 return NoLabel, fmt.Errorf("label parse error: empty name: %q", origStr) 128 } 129 name := strings.TrimPrefix(s, ":") 130 if !labelNameRegexp.MatchString(name) { 131 return NoLabel, fmt.Errorf("label parse error: name has invalid characters: %q", origStr) 132 } 133 134 if pkg == "" && name == "" { 135 return NoLabel, fmt.Errorf("label parse error: empty package and name: %q", origStr) 136 } 137 if name == "" { 138 name = path.Base(pkg) 139 } 140 141 return Label{ 142 Repo: repo, 143 Pkg: pkg, 144 Name: name, 145 Relative: relative, 146 }, nil 147 } 148 149 func (l Label) String() string { 150 if l.Relative { 151 return fmt.Sprintf(":%s", l.Name) 152 } 153 154 var repo string 155 if l.Repo != "" && l.Repo != "@" { 156 repo = fmt.Sprintf("@%s", l.Repo) 157 } else { 158 // if l.Repo == "", the label string will begin with "//" 159 // if l.Repo == "@", the label string will begin with "@//" 160 repo = l.Repo 161 } 162 163 if path.Base(l.Pkg) == l.Name { 164 return fmt.Sprintf("%s//%s", repo, l.Pkg) 165 } 166 return fmt.Sprintf("%s//%s:%s", repo, l.Pkg, l.Name) 167 } 168 169 // Abs computes an absolute label (one with a repository and package name) 170 // from this label. If this label is already absolute, it is returned 171 // unchanged. 172 func (l Label) Abs(repo, pkg string) Label { 173 if !l.Relative { 174 return l 175 } 176 return Label{Repo: repo, Pkg: pkg, Name: l.Name} 177 } 178 179 // Rel attempts to compute a relative label from this label. If this label 180 // is already relative or is in a different package, this label may be 181 // returned unchanged. 182 func (l Label) Rel(repo, pkg string) Label { 183 if l.Relative || l.Repo != repo { 184 return l 185 } 186 if l.Pkg == pkg { 187 return Label{Name: l.Name, Relative: true} 188 } 189 return Label{Pkg: l.Pkg, Name: l.Name} 190 } 191 192 // Equal returns whether two labels are exactly the same. It does not return 193 // true for different labels that refer to the same target. 194 func (l Label) Equal(other Label) bool { 195 return l.Repo == other.Repo && 196 l.Pkg == other.Pkg && 197 l.Name == other.Name && 198 l.Relative == other.Relative 199 } 200 201 // Contains returns whether other is contained by the package of l or a 202 // sub-package. Neither label may be relative. 203 func (l Label) Contains(other Label) bool { 204 if l.Relative { 205 log.Panicf("l must not be relative: %s", l) 206 } 207 if other.Relative { 208 log.Panicf("other must not be relative: %s", other) 209 } 210 result := l.Repo == other.Repo && pathtools.HasPrefix(other.Pkg, l.Pkg) 211 return result 212 } 213 214 func (l Label) BzlExpr() bzl.Expr { 215 return &bzl.StringExpr { 216 Value: l.String(), 217 } 218 } 219 220 var nonWordRe = regexp.MustCompile(`\W+`) 221 222 // ImportPathToBazelRepoName converts a Go import path into a bazel repo name 223 // following the guidelines in http://bazel.io/docs/be/functions.html#workspace 224 func ImportPathToBazelRepoName(importpath string) string { 225 importpath = strings.ToLower(importpath) 226 components := strings.Split(importpath, "/") 227 labels := strings.Split(components[0], ".") 228 reversed := make([]string, 0, len(labels)+len(components)-1) 229 for i := range labels { 230 l := labels[len(labels)-i-1] 231 reversed = append(reversed, l) 232 } 233 repo := strings.Join(append(reversed, components[1:]...), ".") 234 return nonWordRe.ReplaceAllString(repo, "_") 235 }