blob: 277e6058fefd2d0235f63b31c9e43e8a6724ae50 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package exporter
import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/util"
"go.skia.org/skia/bazel/exporter/build_proto/build"
)
const (
ruleOnlyRepoPattern = `^(@\w+)$`
rulePattern = `^(?P<repo>@[^/]+)?/(?P<path>[^:]+)(?P<target>:[\w-\.]+)?$`
locationPattern = `^(?P<path>[^:]+):(?P<line>[^:]+):(?P<pos>[^:]+)$`
)
var (
ruleOnlyRepoRegex = regexp.MustCompile(ruleOnlyRepoPattern)
ruleRegex = regexp.MustCompile(rulePattern)
locRegex = regexp.MustCompile(locationPattern)
)
// Parse a rule into its constituent parts.
// https://docs.bazel.build/versions/main/guide.html#specifying-targets-to-build
func parseRule(rule string) (repo string, path string, target string, err error) {
match := ruleOnlyRepoRegex.FindStringSubmatch(rule)
if match != nil {
return match[1], "/", strings.TrimPrefix(match[1], "@"), nil
}
match = ruleRegex.FindStringSubmatch(rule)
if match == nil {
return "", "", "", skerr.Fmt(`Unable to match rule %q`, rule)
}
if len(match[3]) > 0 {
target = strings.TrimPrefix(match[3], ":")
} else {
// No explicit target, so use directory name as default target.
target = filepath.Base(match[2])
}
return match[1], match[2], target, nil
}
// Parse a file location into its three constituent parts.
//
// A location is of the form:
//
// /full/path/to/BUILD.bazel:33:20
func parseLocation(location string) (path string, line int, pos int, err error) {
match := locRegex.FindStringSubmatch(location)
if match == nil {
return "", 0, 0, skerr.Fmt(`unable to match file location %q`, location)
}
path = match[1]
line, err = strconv.Atoi(match[2])
if err != nil {
return "", 0, 0, skerr.Fmt(`unable to parse line no. %q`, match[2])
}
pos, err = strconv.Atoi(match[3])
if err != nil {
return "", 0, 0, skerr.Fmt(`unable to parse pos. %q`, match[3])
}
return path, line, pos, nil
}
// Return the directory containing the file in the location string.
func getLocationDir(location string) (string, error) {
filePath, _, _, err := parseLocation(location)
if err != nil {
return "", skerr.Wrap(err)
}
return filepath.Dir(filePath), nil
}
func makeCanonicalRuleName(bazelRuleName string) (string, error) {
repo, path, target, err := parseRule(bazelRuleName)
if err != nil {
return "", skerr.Wrap(err)
}
return fmt.Sprintf("%s/%s:%s", repo, path, target), nil
}
// Create a string that uniquely identifies the rule and can be used
// in the exported CMake file as a valid name.
func getRuleCMakeName(bazelRuleName string) (string, error) {
s, err := makeCanonicalRuleName(bazelRuleName)
if err != nil {
return "", skerr.Wrap(err)
}
s = strings.TrimPrefix(s, "//:")
s = strings.TrimPrefix(s, "//")
s = strings.ReplaceAll(s, "//", "_")
s = strings.ReplaceAll(s, "@", "at_")
s = strings.ReplaceAll(s, "/", "_")
s = strings.ReplaceAll(s, ":", "_")
s = strings.ReplaceAll(s, "__", "_")
return s, nil
}
// Append all elements to the slice if not already present in the slice.
func appendUnique(slice []string, elems ...string) []string {
for _, elem := range elems {
if !util.In(elem, slice) {
slice = append(slice, elem)
}
}
return slice
}
// Retrieve (if present) a slice of string attribute values from the given
// rule and attribute name. A nil slice will be returned if the attribute
// does not exist in the rule. A slice of strings (possibly empty) will be
// returned if the attribute is empty. An error will be returned if the
// attribute is not a list type.
func getRuleStringArrayAttribute(r *build.Rule, name string) ([]string, error) {
for _, attrib := range r.Attribute {
if attrib.GetName() != name {
continue
}
if attrib.GetType() != build.Attribute_LABEL_LIST &&
attrib.GetType() != build.Attribute_STRING_LIST {
return nil, skerr.Fmt(`%s in rule %q is not a list`, name, r.GetName())
}
return attrib.GetStringListValue(), nil
}
return nil, nil
}