| /* |
| * Copyright 2020 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Package labels contains helper functions for working with labels. |
| package labels |
| |
| import ( |
| "bytes" |
| "path" |
| "strings" |
| ) |
| |
| // Label represents a Bazel target label. |
| type Label struct { |
| Repository string // Repository of the target, can be empty if the target belongs to the current repository |
| Package string // Package of a target, can be empty for top packages |
| Target string // Name of the target, should be always non-empty |
| } |
| |
| // Format returns a string representation of a label. It's always absolute but |
| // the target name is omitted if it's equal to the package directory, e.g. |
| // "//package/foo:foo" is formatted as "//package/foo". |
| func (l Label) Format() string { |
| b := new(bytes.Buffer) |
| if l.Repository != "" { |
| b.WriteString("@") |
| b.WriteString(l.Repository) |
| } |
| if l.Repository == l.Target && l.Package == "" { |
| return b.String() |
| } |
| b.WriteString("//") |
| b.WriteString(l.Package) |
| if l.Target != path.Base(l.Package) { |
| b.WriteString(":") |
| b.WriteString(l.Target) |
| } |
| return b.String() |
| } |
| |
| // FormatRelative returns a string representation of a label relative to `pkg` |
| // (relative label if it represents a target in the same package, absolute otherwise) |
| func (l Label) FormatRelative(pkg string) string { |
| if l.Repository != "" || pkg != l.Package { |
| // External repository or different package |
| return l.Format() |
| } |
| return ":" + l.Target |
| } |
| |
| // Parse parses an absolute Bazel label (eg. //devtools/buildozer:rule) |
| // and returns the corresponding Label object. |
| func Parse(target string) Label { |
| label := Label{} |
| if strings.HasPrefix(target, "@") { |
| target = strings.TrimLeft(target, "@") |
| parts := strings.SplitN(target, "/", 2) |
| if len(parts) == 1 { |
| // "@foo" -> @foo//:foo |
| return Label{target, "", target} |
| } |
| label.Repository = parts[0] |
| target = "/" + parts[1] |
| } |
| parts := strings.SplitN(target, ":", 2) |
| parts[0] = strings.TrimPrefix(parts[0], "//") |
| label.Package = parts[0] |
| if len(parts) == 2 && parts[1] != "" { |
| label.Target = parts[1] |
| } else if !strings.HasPrefix(target, "//") { |
| // Maybe not really a label, store everything in Target |
| label.Target = target |
| label.Package = "" |
| } else { |
| // "//absolute/pkg" -> "absolute/pkg", "pkg" |
| label.Target = path.Base(parts[0]) |
| } |
| return label |
| } |
| |
| // ParseRelative parses a label `input` which may be absolute or relative. |
| // If it's relative then it's considered to belong to `pkg` |
| func ParseRelative(input, pkg string) Label { |
| if !strings.HasPrefix(input, "@") && !strings.HasPrefix(input, "//") { |
| return Label{Package: pkg, Target: strings.TrimLeft(input, ":")} |
| } |
| return Parse(input) |
| } |
| |
| // Shorten rewrites labels to use the canonical form (the form |
| // recommended by build-style). |
| // "//foo/bar:bar" => "//foo/bar", or ":bar" if the label belongs to pkg |
| func Shorten(input, pkg string) string { |
| if !strings.HasPrefix(input, "//") && !strings.HasPrefix(input, "@") { |
| // It doesn't look like a long label, so we preserve it. |
| // Maybe it's not a label at all, e.g. a filename. |
| return input |
| } |
| label := Parse(input) |
| return label.FormatRelative(pkg) |
| } |
| |
| // Equal returns true if label1 and label2 are equal. The function |
| // takes care of the optional ":" prefix and differences between long-form |
| // labels and local labels (relative to pkg). |
| func Equal(label1, label2, pkg string) bool { |
| return ParseRelative(label1, pkg) == ParseRelative(label2, pkg) |
| } |