/* * 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) }