github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/resolve/resolve.go (about) 1 /* Copyright 2016 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 "fmt" 20 "go/build" 21 "log" 22 "path" 23 "strings" 24 25 "github.com/bazelbuild/bazel-gazelle/internal/config" 26 "github.com/bazelbuild/bazel-gazelle/internal/label" 27 "github.com/bazelbuild/bazel-gazelle/internal/pathtools" 28 "github.com/bazelbuild/bazel-gazelle/internal/repos" 29 bf "github.com/bazelbuild/buildtools/build" 30 ) 31 32 // Resolver resolves import strings in source files (import paths in Go, 33 // import statements in protos) into Bazel labels. 34 type Resolver struct { 35 c *config.Config 36 l *label.Labeler 37 ix *RuleIndex 38 external nonlocalResolver 39 } 40 41 // nonlocalResolver resolves import paths outside of the current repository's 42 // prefix. Once we have smarter import path resolution, this shouldn't 43 // be necessary, and we can remove this abstraction. 44 type nonlocalResolver interface { 45 resolve(imp string) (label.Label, error) 46 } 47 48 func NewResolver(c *config.Config, l *label.Labeler, ix *RuleIndex, rc *repos.RemoteCache) *Resolver { 49 var e nonlocalResolver 50 switch c.DepMode { 51 case config.ExternalMode: 52 e = newExternalResolver(l, rc) 53 case config.VendorMode: 54 e = newVendoredResolver(l) 55 } 56 57 return &Resolver{ 58 c: c, 59 l: l, 60 ix: ix, 61 external: e, 62 } 63 } 64 65 // ResolveRule copies and modifies a generated rule e by replacing the import 66 // paths in the "_gazelle_imports" attribute with labels in a "deps" 67 // attribute. This may be safely called on expressions that aren't Go rules 68 // (the original expression will be returned). Any existing "deps" attribute 69 // is deleted, so it may be necessary to merge the result. 70 func (r *Resolver) ResolveRule(e bf.Expr, pkgRel string) bf.Expr { 71 call, ok := e.(*bf.CallExpr) 72 if !ok { 73 return e 74 } 75 rule := bf.Rule{Call: call} 76 from := label.New("", pkgRel, rule.Name()) 77 78 var resolve func(imp string, from label.Label) (label.Label, error) 79 switch rule.Kind() { 80 case "go_library", "go_binary", "go_test": 81 resolve = r.resolveGo 82 case "proto_library": 83 resolve = r.resolveProto 84 case "go_proto_library", "go_grpc_library": 85 resolve = r.resolveGoProto 86 default: 87 return e 88 } 89 90 resolved := *call 91 resolved.List = append([]bf.Expr{}, call.List...) 92 rule.Call = &resolved 93 94 imports := rule.Attr(config.GazelleImportsKey) 95 rule.DelAttr(config.GazelleImportsKey) 96 rule.DelAttr("deps") 97 deps := mapExprStrings(imports, func(imp string) string { 98 label, err := resolve(imp, from) 99 if err != nil { 100 switch err.(type) { 101 case standardImportError, selfImportError: 102 return "" 103 default: 104 log.Print(err) 105 return "" 106 } 107 } 108 label.Relative = label.Repo == "" && label.Pkg == pkgRel 109 return label.String() 110 }) 111 if deps != nil { 112 rule.SetAttr("deps", deps) 113 } 114 115 return &resolved 116 } 117 118 type standardImportError struct { 119 imp string 120 } 121 122 func (e standardImportError) Error() string { 123 return fmt.Sprintf("import path %q is in the standard library", e.imp) 124 } 125 126 // mapExprStrings applies a function f to the strings in e and returns a new 127 // expression with the results. Scalar strings, lists, dicts, selects, and 128 // concatenations are supported. 129 func mapExprStrings(e bf.Expr, f func(string) string) bf.Expr { 130 if e == nil { 131 return nil 132 } 133 switch expr := e.(type) { 134 case *bf.StringExpr: 135 s := f(expr.Value) 136 if s == "" { 137 return nil 138 } 139 ret := *expr 140 ret.Value = s 141 return &ret 142 143 case *bf.ListExpr: 144 var list []bf.Expr 145 for _, elem := range expr.List { 146 elem = mapExprStrings(elem, f) 147 if elem != nil { 148 list = append(list, elem) 149 } 150 } 151 if len(list) == 0 && len(expr.List) > 0 { 152 return nil 153 } 154 ret := *expr 155 ret.List = list 156 return &ret 157 158 case *bf.DictExpr: 159 var cases []bf.Expr 160 isEmpty := true 161 for _, kv := range expr.List { 162 keyval, ok := kv.(*bf.KeyValueExpr) 163 if !ok { 164 log.Panicf("unexpected expression in generated imports dict: %#v", kv) 165 } 166 value := mapExprStrings(keyval.Value, f) 167 if value != nil { 168 cases = append(cases, &bf.KeyValueExpr{Key: keyval.Key, Value: value}) 169 if key, ok := keyval.Key.(*bf.StringExpr); !ok || key.Value != "//conditions:default" { 170 isEmpty = false 171 } 172 } 173 } 174 if isEmpty { 175 return nil 176 } 177 ret := *expr 178 ret.List = cases 179 return &ret 180 181 case *bf.CallExpr: 182 if x, ok := expr.X.(*bf.LiteralExpr); !ok || x.Token != "select" || len(expr.List) != 1 { 183 log.Panicf("unexpected call expression in generated imports: %#v", e) 184 } 185 arg := mapExprStrings(expr.List[0], f) 186 if arg == nil { 187 return nil 188 } 189 call := *expr 190 call.List[0] = arg 191 return &call 192 193 case *bf.BinaryExpr: 194 x := mapExprStrings(expr.X, f) 195 y := mapExprStrings(expr.Y, f) 196 if x == nil { 197 return y 198 } 199 if y == nil { 200 return x 201 } 202 binop := *expr 203 binop.X = x 204 binop.Y = y 205 return &binop 206 207 default: 208 log.Panicf("unexpected expression in generated imports: %#v", e) 209 return nil 210 } 211 } 212 213 // resolveGo resolves an import path from a Go source file to a label. 214 // pkgRel is the path to the Go package relative to the repository root; it 215 // is used to resolve relative imports. 216 func (r *Resolver) resolveGo(imp string, from label.Label) (label.Label, error) { 217 if build.IsLocalImport(imp) { 218 cleanRel := path.Clean(path.Join(from.Pkg, imp)) 219 if build.IsLocalImport(cleanRel) { 220 return label.NoLabel, fmt.Errorf("relative import path %q from %q points outside of repository", imp, from.Pkg) 221 } 222 imp = path.Join(r.c.GoPrefix, cleanRel) 223 } 224 225 if IsStandard(imp) { 226 return label.NoLabel, standardImportError{imp} 227 } 228 229 if l, err := r.ix.findLabelByImport(importSpec{config.GoLang, imp}, config.GoLang, from); err != nil { 230 if _, ok := err.(ruleNotFoundError); !ok { 231 return label.NoLabel, err 232 } 233 } else { 234 return l, nil 235 } 236 237 if pathtools.HasPrefix(imp, r.c.GoPrefix) { 238 return r.l.LibraryLabel(pathtools.TrimPrefix(imp, r.c.GoPrefix)), nil 239 } 240 241 return r.external.resolve(imp) 242 } 243 244 const ( 245 wellKnownPrefix = "google/protobuf/" 246 wellKnownGoProtoPkg = "ptypes" 247 descriptorPkg = "protoc-gen-go/descriptor" 248 ) 249 250 // resolveProto resolves an import statement in a .proto file to a label 251 // for a proto_library rule. 252 func (r *Resolver) resolveProto(imp string, from label.Label) (label.Label, error) { 253 if !strings.HasSuffix(imp, ".proto") { 254 return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp) 255 } 256 if isWellKnown(imp) { 257 name := path.Base(imp[:len(imp)-len(".proto")]) + "_proto" 258 return label.New(config.WellKnownTypesProtoRepo, "", name), nil 259 } 260 261 if l, err := r.ix.findLabelByImport(importSpec{config.ProtoLang, imp}, config.ProtoLang, from); err != nil { 262 if _, ok := err.(ruleNotFoundError); !ok { 263 return label.NoLabel, err 264 } 265 } else { 266 return l, nil 267 } 268 269 rel := path.Dir(imp) 270 if rel == "." { 271 rel = "" 272 } 273 name := pathtools.RelBaseName(rel, r.c.GoPrefix, r.c.RepoRoot) 274 return r.l.ProtoLabel(rel, name), nil 275 } 276 277 // resolveGoProto resolves an import statement in a .proto file to a 278 // label for a go_library rule that embeds the corresponding go_proto_library. 279 func (r *Resolver) resolveGoProto(imp string, from label.Label) (label.Label, error) { 280 if !strings.HasSuffix(imp, ".proto") { 281 return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp) 282 } 283 stem := imp[:len(imp)-len(".proto")] 284 285 if isWellKnown(stem) { 286 return label.NoLabel, standardImportError{imp} 287 } 288 289 if l, err := r.ix.findLabelByImport(importSpec{config.ProtoLang, imp}, config.GoLang, from); err != nil { 290 if _, ok := err.(ruleNotFoundError); !ok { 291 return label.NoLabel, err 292 } 293 } else { 294 return l, err 295 } 296 297 // As a fallback, guess the label based on the proto file name. We assume 298 // all proto files in a directory belong to the same package, and the 299 // package name matches the directory base name. We also assume that protos 300 // in the vendor directory must refer to something else in vendor. 301 rel := path.Dir(imp) 302 if rel == "." { 303 rel = "" 304 } 305 if from.Pkg == "vendor" || strings.HasPrefix(from.Pkg, "vendor/") { 306 rel = path.Join("vendor", rel) 307 } 308 return r.l.LibraryLabel(rel), nil 309 } 310 311 // IsStandard returns whether a package is in the standard library. 312 func IsStandard(imp string) bool { 313 return stdPackages[imp] 314 } 315 316 func isWellKnown(imp string) bool { 317 return strings.HasPrefix(imp, wellKnownPrefix) && strings.TrimPrefix(imp, wellKnownPrefix) == path.Base(imp) 318 }