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 }