github.com/wolfd/bazel-gazelle@v0.14.0/internal/resolve/index.go (about)

     1  /* Copyright 2018 The Bazel Authors. All rights reserved.
     2  
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package resolve
    17  
    18  import (
    19  	"log"
    20  
    21  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    22  	"github.com/bazelbuild/bazel-gazelle/internal/label"
    23  	"github.com/bazelbuild/bazel-gazelle/internal/repos"
    24  	"github.com/bazelbuild/bazel-gazelle/internal/rule"
    25  )
    26  
    27  // ImportSpec describes a library to be imported. Imp is an import string for
    28  // the library. Lang is the language in which the import string appears (this
    29  // should match Resolver.Name).
    30  type ImportSpec struct {
    31  	Lang, Imp string
    32  }
    33  
    34  // Resolver is an interface that language extensions can implement to resolve
    35  // dependencies in rules they generate.
    36  type Resolver interface {
    37  	// Name returns the name of the language. This should be a prefix of the
    38  	// kinds of rules generated by the language, e.g., "go" for the Go extension
    39  	// since it generates "go_library" rules.
    40  	Name() string
    41  
    42  	// Imports returns a list of ImportSpecs that can be used to import the rule
    43  	// r. This is used to populate RuleIndex.
    44  	//
    45  	// If nil is returned, the rule will not be indexed. If any non-nil slice is
    46  	// returned, including an empty slice, the rule will be indexed.
    47  	Imports(c *config.Config, r *rule.Rule, f *rule.File) []ImportSpec
    48  
    49  	// Embeds returns a list of labels of rules that the given rule embeds. If
    50  	// a rule is embedded by another importable rule of the same language, only
    51  	// the embedding rule will be indexed. The embedding rule will inherit
    52  	// the imports of the embedded rule.
    53  	Embeds(r *rule.Rule, from label.Label) []label.Label
    54  
    55  	// Resolve translates imported libraries for a given rule into Bazel
    56  	// dependencies. A list of imported libraries is typically stored in a
    57  	// private attribute of the rule when it's generated (this interface doesn't
    58  	// dictate how that is stored or represented). Resolve generates a "deps"
    59  	// attribute (or the appropriate language-specific equivalent) for each
    60  	// import according to language-specific rules and heuristics.
    61  	Resolve(c *config.Config, ix *RuleIndex, rc *repos.RemoteCache, r *rule.Rule, from label.Label)
    62  }
    63  
    64  // RuleIndex is a table of rules in a workspace, indexed by label and by
    65  // import path. Used by Resolver to map import paths to labels.
    66  type RuleIndex struct {
    67  	rules          []*ruleRecord
    68  	labelMap       map[label.Label]*ruleRecord
    69  	importMap      map[ImportSpec][]*ruleRecord
    70  	kindToResolver map[string]Resolver
    71  }
    72  
    73  // ruleRecord contains information about a rule relevant to import indexing.
    74  type ruleRecord struct {
    75  	rule  *rule.Rule
    76  	label label.Label
    77  
    78  	// importedAs is a list of ImportSpecs by which this rule may be imported.
    79  	// Used to build a map from ImportSpecs to ruleRecords.
    80  	importedAs []ImportSpec
    81  
    82  	// embeds is the transitive closure of labels for rules that this rule embeds
    83  	// (as determined by the Embeds method). This only includes rules in the same
    84  	// language (i.e., it includes a go_library embedding a go_proto_library, but
    85  	// not a go_proto_library embedding a proto_library).
    86  	embeds []label.Label
    87  
    88  	// embedded indicates whether another rule of the same language embeds this
    89  	// rule. Embedded rules should not be indexed.
    90  	embedded bool
    91  
    92  	didCollectEmbeds bool
    93  }
    94  
    95  // NewRuleIndex creates a new index.
    96  //
    97  // kindToResolver is a map from rule kinds (for example, "go_library") to
    98  // Resolvers that support those kinds.
    99  func NewRuleIndex(kindToResolver map[string]Resolver) *RuleIndex {
   100  	return &RuleIndex{
   101  		labelMap:       make(map[label.Label]*ruleRecord),
   102  		kindToResolver: kindToResolver,
   103  	}
   104  }
   105  
   106  // AddRule adds a rule r to the index. The rule will only be indexed if there
   107  // is a known resolver for the rule's kind and Resolver.Imports returns a
   108  // non-nil slice.
   109  //
   110  // AddRule may only be called before Finish.
   111  func (ix *RuleIndex) AddRule(c *config.Config, r *rule.Rule, f *rule.File) {
   112  	var imps []ImportSpec
   113  	if rslv, ok := ix.kindToResolver[r.Kind()]; ok {
   114  		imps = rslv.Imports(c, r, f)
   115  	}
   116  	// If imps == nil, the rule is not importable. If imps is the empty slice,
   117  	// it may still be importable if it embeds importable libraries.
   118  	if imps == nil {
   119  		return
   120  	}
   121  
   122  	record := &ruleRecord{
   123  		rule:       r,
   124  		label:      label.New(c.RepoName, f.Pkg, r.Name()),
   125  		importedAs: imps,
   126  	}
   127  	if _, ok := ix.labelMap[record.label]; ok {
   128  		log.Printf("multiple rules found with label %s", record.label)
   129  		return
   130  	}
   131  	ix.rules = append(ix.rules, record)
   132  	ix.labelMap[record.label] = record
   133  }
   134  
   135  // Finish constructs the import index and performs any other necessary indexing
   136  // actions after all rules have been added. This step is necessary because
   137  // a rule may be indexed differently based on what rules are added later.
   138  //
   139  // Finish must be called after all AddRule calls and before any
   140  // FindRulesByImport calls.
   141  func (ix *RuleIndex) Finish() {
   142  	for _, r := range ix.rules {
   143  		ix.collectEmbeds(r)
   144  	}
   145  	ix.buildImportIndex()
   146  }
   147  
   148  func (ix *RuleIndex) collectEmbeds(r *ruleRecord) {
   149  	if r.didCollectEmbeds {
   150  		return
   151  	}
   152  	r.didCollectEmbeds = true
   153  	embedLabels := ix.kindToResolver[r.rule.Kind()].Embeds(r.rule, r.label)
   154  	r.embeds = embedLabels
   155  	for _, e := range embedLabels {
   156  		er, ok := ix.findRuleByLabel(e, r.label)
   157  		if !ok {
   158  			continue
   159  		}
   160  		ix.collectEmbeds(er)
   161  		if ix.kindToResolver[r.rule.Kind()] == ix.kindToResolver[er.rule.Kind()] {
   162  			er.embedded = true
   163  			r.embeds = append(r.embeds, er.embeds...)
   164  		}
   165  		r.importedAs = append(r.importedAs, er.importedAs...)
   166  	}
   167  }
   168  
   169  // buildImportIndex constructs the map used by FindRulesByImport.
   170  func (ix *RuleIndex) buildImportIndex() {
   171  	ix.importMap = make(map[ImportSpec][]*ruleRecord)
   172  	for _, r := range ix.rules {
   173  		if r.embedded {
   174  			continue
   175  		}
   176  		indexed := make(map[ImportSpec]bool)
   177  		for _, imp := range r.importedAs {
   178  			if indexed[imp] {
   179  				continue
   180  			}
   181  			indexed[imp] = true
   182  			ix.importMap[imp] = append(ix.importMap[imp], r)
   183  		}
   184  	}
   185  }
   186  
   187  func (ix *RuleIndex) findRuleByLabel(label label.Label, from label.Label) (*ruleRecord, bool) {
   188  	label = label.Abs(from.Repo, from.Pkg)
   189  	r, ok := ix.labelMap[label]
   190  	return r, ok
   191  }
   192  
   193  type FindResult struct {
   194  	// Label is the absolute label (including repository and package name) for
   195  	// a matched rule.
   196  	Label label.Label
   197  
   198  	Rule *rule.Rule
   199  
   200  	// Embeds is the transitive closure of labels for rules that the matched
   201  	// rule embeds. It may contains duplicates and does not include the label
   202  	// for the rule itself.
   203  	Embeds []label.Label
   204  }
   205  
   206  // FindRulesByImport attempts to resolve an import string to a rule record.
   207  // imp is the import to resolve (which includes the target language). lang is
   208  // the language of the rule with the dependency (for example, in
   209  // go_proto_library, imp will have ProtoLang and lang will be GoLang).
   210  // from is the rule which is doing the dependency. This is used to check
   211  // vendoring visibility and to check for self-imports.
   212  //
   213  // FindRulesByImport returns a list of rules, since any number of rules may
   214  // provide the same import. Callers may need to resolve ambiguities using
   215  // language-specific heuristics.
   216  func (ix *RuleIndex) FindRulesByImport(imp ImportSpec, lang string) []FindResult {
   217  	matches := ix.importMap[imp]
   218  	results := make([]FindResult, 0, len(matches))
   219  	for _, m := range matches {
   220  		if ix.kindToResolver[m.rule.Kind()].Name() != lang {
   221  			continue
   222  		}
   223  		results = append(results, FindResult{
   224  			Label:  m.label,
   225  			Rule:   m.rule,
   226  			Embeds: m.embeds,
   227  		})
   228  	}
   229  	return results
   230  }
   231  
   232  // IsSelfImport returns true if the result's label matches the given label
   233  // or the result's rule transitively embeds the rule with the given label.
   234  // Self imports cause cyclic dependencies, so the caller may want to omit
   235  // the dependency or report an error.
   236  func (r FindResult) IsSelfImport(from label.Label) bool {
   237  	if from.Equal(r.Label) {
   238  		return true
   239  	}
   240  	for _, e := range r.Embeds {
   241  		if from.Equal(e) {
   242  			return true
   243  		}
   244  	}
   245  	return false
   246  }