blob: 756462160f39cf4452111eb76eefb3dccc859bf0 [file] [log] [blame] [edit]
/*
* 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)
}