blob: 975438f48cb0203ebee8bbdae798d5755ca2bc78 [file] [log] [blame]
package resolver
import (
"log"
"sort"
"strings"
"github.com/bazelbuild/bazel-gazelle/config"
"github.com/bazelbuild/bazel-gazelle/label"
"github.com/bazelbuild/bazel-gazelle/repo"
"github.com/bazelbuild/bazel-gazelle/resolve"
"github.com/bazelbuild/bazel-gazelle/rule"
"go.skia.org/infra/bazel/gazelle/cpp/common"
"go.skia.org/infra/go/util"
)
const (
// gazelleExtensionName is the extension name passed to Gazelle.
//
// This name can be used to enable or disable this Gazelle extension via the --lang flag, e.g.
//
// $ bazel run gazelle -- update --lang go,cpp
gazelleExtensionName = "cpp"
// If a third_party dep starts with this prefix, the file will be ignored and not included
// in the generated rule. A common use is when a #include is needed for a non-Bazel build.
ignoreThirdPartyFilePrefix = "SK_GAZELLE_IGNORE"
)
// CppResolver implements the resolve.Resolver interface.
//
// Interface documentation:
//
// CppResolver is an interface that language extensions can implement to resolve
// dependencies in rules they generate.
type CppResolver struct{}
// Name implements the resolve.Resolver interface.
//
// Interface documentation:
//
// Name returns the name of the language. This should be a prefix of the
// kinds of rules generated by the language, e.g., "go" for the Go extension
// since it generates "go_library" rules.
func (rslv *CppResolver) Name() string {
return gazelleExtensionName
}
// Imports implements the resolve.Resolver interface.
//
// This method always returns an empty slice, which results in an empty resolve.RuleIndex,
// but that is OK because we do not use it.
func (rslv *CppResolver) Imports(*config.Config, *rule.Rule, *rule.File) []resolve.ImportSpec {
return nil
}
// Embeds implements the resolve.Resolver interface.
func (rslv *CppResolver) Embeds(*rule.Rule, label.Label) []label.Label { return nil }
// Resolve implements the resolve.Resolver interface.
//
// Resolve takes a (rule, ImportsParsedFromRuleSources) pair generated by Language.GenerateRules()
// and resolves the rule's dependencies based on its imports. It populates the rule's deps argument
// with the result of mapping each import to the label of a rule that provides the import. It does
// so by using the convention that each generated_cc_atom for which this rule depends on will
// expose a similarly named file. For example, needing "src/foo/Bar.h" assumes that a rule with
// label "//src/foo:Bar_hdr" exists.
//
// Gazelle calls this method once for each (rule, ImportsParsedFromRuleSources) pair generated by
// successive calls to Language.GenerateRules().
func (rslv *CppResolver) Resolve(_ *config.Config, _ *resolve.RuleIndex, _ *repo.RemoteCache, r *rule.Rule, imports interface{}, from label.Label) {
importsFromRuleSources := imports.(common.ImportsParsedFromRuleSources)
thirdPartyMap := importsFromRuleSources.GetThirdPartyMap()
switch r.Kind() {
case common.GeneratedCCAtomRule:
var deps []label.Label
// Look through repo includes for thirdPartyMap matches first. Anything not in third party
// is assumed to be another header or source file in the repo.
for _, importPath := range importsFromRuleSources.GetRepoIncludes() {
if d, ok := thirdPartyMap[importPath]; ok {
if strings.HasPrefix(d, ignoreThirdPartyFilePrefix) {
continue
}
deps = append(deps, thirdPartyDep(d))
continue
}
newLabel := resolveDepsForCImport(importPath)
if newLabel.Equal(label.NoLabel) {
log.Printf("Could not resolve %s in %s. Maybe it needs a third_party entry?\n", importPath, from.String())
continue
}
newLabel.Repo = from.Repo
deps = append(deps, newLabel)
}
// Look through system includes for anything in the thirdPartyMap - skip it otherwise.
for _, importPath := range importsFromRuleSources.GetSystemIncludes() {
if d, ok := thirdPartyMap[importPath]; ok {
if strings.HasPrefix(d, ignoreThirdPartyFilePrefix) {
continue
}
deps = append(deps, thirdPartyDep(d))
}
}
setDeps(r, from, deps)
default:
panic("Unknown rule kind " + r.Kind())
}
}
// thirdPartyDep parses a label string into a proper Label.
func thirdPartyDep(d string) label.Label {
lbl, err := label.Parse(d)
if err != nil {
log.Fatalf("Invalid third_party dep %s - %s", d, err)
}
return lbl
}
// setDeps sets the dependencies of a rule.
func setDeps(r *rule.Rule, thisLabel label.Label, deps []label.Label) {
const depsAttr = "deps"
r.DelAttr(depsAttr)
var depsAsStrings []string
for _, dep := range deps {
// Filter out self-imports, e.g. when the .cpp file includes the .h file.
if thisLabel.Equal(dep) {
continue
}
// Try to use a relative include if possible
dep = dep.Rel(thisLabel.Repo, thisLabel.Pkg)
depsAsStrings = append(depsAsStrings, dep.String())
}
if len(depsAsStrings) > 0 {
depsAsStrings = util.SSliceDedup(depsAsStrings)
sort.Strings(depsAsStrings)
r.SetAttr(depsAttr, depsAsStrings)
}
}
// resolveDepsForCImport takes a given path to a file and assumes that it is available by a rule
// with the same name, except the file extension is removed.
// For example, the file "alpha/beta/gamma/Delta.h" is assumed to be in the package
// "alpha/beta/gamma" with a rule called "Delta".
func resolveDepsForCImport(importPath string) label.Label {
idx := strings.LastIndex(importPath, "/")
if idx < 0 {
return label.NoLabel
}
pkg := importPath[:idx]
r := importPath[idx+1:]
if common.IsCppHeader(r) {
return label.New("", pkg, common.TrimCSuffix(r)+"_hdr")
}
return label.New("", pkg, common.TrimCSuffix(r)+"_src")
}
var _ resolve.Resolver = &CppResolver{}