blob: 9671a736a60d4a0b0a112f90dd12c31c7a554270 [file] [log] [blame]
package language
import (
"io/ioutil"
"log"
"path/filepath"
"sort"
"github.com/bazelbuild/bazel-gazelle/config"
"github.com/bazelbuild/bazel-gazelle/language"
"github.com/bazelbuild/bazel-gazelle/rule"
"go.skia.org/infra/bazel/gazelle/cpp/common"
"go.skia.org/infra/bazel/gazelle/cpp/configurer"
"go.skia.org/infra/bazel/gazelle/cpp/parsers"
"go.skia.org/infra/bazel/gazelle/cpp/resolver"
)
// CppLanguage implements the language.CppLanguage interface.
type CppLanguage struct {
configurer.CppConfigurer
resolver.CppResolver
}
// Kinds implements the language.CppLanguage interface.
//
// Interface documentation:
//
// Kinds returns a map of maps rule names (kinds) and information on how to
// match and merge attributes that may be found in rules of those kinds. All
// kinds of rules generated for this language may be found here.
func (l *CppLanguage) Kinds() map[string]rule.KindInfo {
return map[string]rule.KindInfo{
common.GeneratedCCAtomRule: {
// This extension generates content for deps, hdrs, and srcs, so that's why they are
// specified here.
ResolveAttrs: map[string]bool{"deps": true, "hdrs": true, "srcs": true},
// NonEmptyAttrs must be non-nil, otherwise no rules will get deleted.
// We don't have any fields that should prevent a rule from being deleted if the
// underlying source file or header file is missing, so an empty map suffices.
NonEmptyAttrs: map[string]bool{},
},
}
}
// Loads implements the language.CppLanguage interface.
//
// Interface documentation:
//
// Loads returns .bzl files and symbols they define. Every rule generated by
// GenerateRules, now or in the past, should be loadable from one of these
// files.
func (l *CppLanguage) Loads() []rule.LoadInfo {
return []rule.LoadInfo{
{
Name: "//bazel:macros.bzl",
Symbols: []string{
common.GeneratedCCAtomRule,
},
},
}
}
// GenerateRules implements the language.CppLanguage interface.
//
// GenerateRules generates build rules for source files in a directory. GenerateRules is called in
// each directory where an update is requested in depth-first order.
//
// This method does not populate the deps argument of any generate rules. Dependencies are resolved
// in CppResolver.Resolve(), which happens after GenerateRules is called in each directory where an
// update is requested.
func (l *CppLanguage) GenerateRules(args language.GenerateArgs) language.GenerateResult {
allFiles := append(args.RegularFiles, args.GenFiles...)
rules := makeRulesFromFiles(allFiles)
imports := getImportsFromRules(rules, args.Dir, l.ThirdPartyFileMap)
var existingRules []*rule.Rule
if args.File != nil {
existingRules = args.File.Rules
}
return language.GenerateResult{
Gen: rules,
Imports: imports,
Empty: findEmptyRules(existingRules, rules),
}
}
// findEmptyRules returns any generated_cc_atom rules which were in the rules from BUILD.bazel,
// but did not show up when we recreated all the rules from the source files. This means the
// underlying source files have been deleted or moved and the rules should as well.
func findEmptyRules(previous, new []*rule.Rule) []*rule.Rule {
ruleCache := map[string]bool{}
for _, r := range new {
if r.Kind() != common.GeneratedCCAtomRule {
continue
}
ruleCache[r.Name()] = true
}
var empty []*rule.Rule
for _, r := range previous {
if r.Kind() != common.GeneratedCCAtomRule {
continue
}
if _, ok := ruleCache[r.Name()]; !ok {
empty = append(empty, rule.NewRule(common.GeneratedCCAtomRule, r.Name()))
}
}
return empty
}
// makeRulesFromFiles returns a single rule per file. Header rules will have the _hdr suffix and
// source rules will have the _src suffix so as to distinguish them, but have them be named
// in a predictable way.
func makeRulesFromFiles(names []string) []*rule.Rule {
var rv []*rule.Rule
for _, name := range names {
var r *rule.Rule
if common.IsCppHeader(name) {
r = rule.NewRule(common.GeneratedCCAtomRule, common.TrimCSuffix(name)+"_hdr")
r.SetAttr("hdrs", []string{name})
} else if common.IsCppSource(name) {
r = rule.NewRule(common.GeneratedCCAtomRule, common.TrimCSuffix(name)+"_src")
r.SetAttr("srcs", []string{name})
} else {
continue
}
r.SetAttr("visibility", []string{"//:__subpackages__"})
rv = append(rv, r)
}
// Sort for determinism
sort.Slice(rv, func(i, j int) bool {
return rv[i].Name() < rv[j].Name()
})
return rv
}
// getImportsFromRules returns a list of common.ImportsParsedFromRuleSources, one per rule, that
// relate to the files that were listed via #include.
func getImportsFromRules(rules []*rule.Rule, dir string, thirdPartyMap map[string]string) []interface{} {
var rv []interface{}
for _, r := range rules {
var repoHeaders, systemHeaders []string
hdr := r.AttrStrings("hdrs")
if len(hdr) == 1 {
rh, sh := extractImportsFromCFile(filepath.Join(dir, hdr[0]))
repoHeaders = append(repoHeaders, rh...)
systemHeaders = append(systemHeaders, sh...)
}
src := r.AttrStrings("srcs")
if len(src) == 1 {
rh, sh := extractImportsFromCFile(filepath.Join(dir, src[0]))
repoHeaders = append(repoHeaders, rh...)
systemHeaders = append(systemHeaders, sh...)
}
rv = append(rv, common.NewImports(repoHeaders, systemHeaders, thirdPartyMap))
}
return rv
}
// extractImportsFromCFile opens ups the given file and parses the contents.
func extractImportsFromCFile(path string) ([]string, []string) {
b, err := ioutil.ReadFile(path)
if err != nil {
log.Panicf("Error reading file %q: %v", path, err)
}
return parsers.ParseCIncludes(string(b[:]))
}
// Fix implements the language.CppLanguage interface.
func (l *CppLanguage) Fix(*config.Config, *rule.File) {}
var _ language.Language = &CppLanguage{}