1 /* 2 * Copyright 2020 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package labels contains helper functions for working with labels. 18 package labels 19 20 import ( 21 "bytes" 22 "path" 23 "strings" 24 ) 25 26 // Label represents a Bazel target label. 27 type Label struct { 28 Repository string // Repository of the target, can be empty if the target belongs to the current repository 29 Package string // Package of a target, can be empty for top packages 30 Target string // Name of the target, should be always non-empty 31 } 32 33 // Format returns a string representation of a label. It's always absolute but 34 // the target name is omitted if it's equal to the package directory, e.g. 35 // "//package/foo:foo" is formatted as "//package/foo". 36 func (l Label) Format() string { 37 b := new(bytes.Buffer) 38 if l.Repository != "" { 39 b.WriteString("@") 40 b.WriteString(l.Repository) 41 } 42 if l.Repository == l.Target && l.Package == "" { 43 return b.String() 44 } 45 b.WriteString("//") 46 b.WriteString(l.Package) 47 if l.Target != path.Base(l.Package) { 48 b.WriteString(":") 49 b.WriteString(l.Target) 50 } 51 return b.String() 52 } 53 54 // FormatRelative returns a string representation of a label relative to `pkg` 55 // (relative label if it represents a target in the same package, absolute otherwise) 56 func (l Label) FormatRelative(pkg string) string { 57 if l.Repository != "" || pkg != l.Package { 58 // External repository or different package 59 return l.Format() 60 } 61 return ":" + l.Target 62 } 63 64 // Parse parses an absolute Bazel label (eg. //devtools/buildozer:rule) 65 // and returns the corresponding Label object. 66 func Parse(target string) Label { 67 label := Label{} 68 if strings.HasPrefix(target, "@") { 69 target = strings.TrimLeft(target, "@") 70 parts := strings.SplitN(target, "/", 2) 71 if len(parts) == 1 { 72 // "@foo" -> @foo//:foo 73 return Label{target, "", target} 74 } 75 label.Repository = parts[0] 76 target = "/" + parts[1] 77 } 78 parts := strings.SplitN(target, ":", 2) 79 parts[0] = strings.TrimPrefix(parts[0], "//") 80 label.Package = parts[0] 81 if len(parts) == 2 && parts[1] != "" { 82 label.Target = parts[1] 83 } else if !strings.HasPrefix(target, "//") { 84 // Maybe not really a label, store everything in Target 85 label.Target = target 86 label.Package = "" 87 } else { 88 // "//absolute/pkg" -> "absolute/pkg", "pkg" 89 label.Target = path.Base(parts[0]) 90 } 91 return label 92 } 93 94 // ParseRelative parses a label `input` which may be absolute or relative. 95 // If it's relative then it's considered to belong to `pkg` 96 func ParseRelative(input, pkg string) Label { 97 if !strings.HasPrefix(input, "@") && !strings.HasPrefix(input, "//") { 98 return Label{Package: pkg, Target: strings.TrimLeft(input, ":")} 99 } 100 return Parse(input) 101 } 102 103 // Shorten rewrites labels to use the canonical form (the form 104 // recommended by build-style). 105 // "//foo/bar:bar" => "//foo/bar", or ":bar" if the label belongs to pkg 106 func Shorten(input, pkg string) string { 107 if !strings.HasPrefix(input, "//") && !strings.HasPrefix(input, "@") { 108 // It doesn't look like a long label, so we preserve it. 109 // Maybe it's not a label at all, e.g. a filename. 110 return input 111 } 112 label := Parse(input) 113 return label.FormatRelative(pkg) 114 } 115 116 // Equal returns true if label1 and label2 are equal. The function 117 // takes care of the optional ":" prefix and differences between long-form 118 // labels and local labels (relative to pkg). 119 func Equal(label1, label2, pkg string) bool { 120 return ParseRelative(label1, pkg) == ParseRelative(label2, pkg) 121 } 122