github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/resolve.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 golang 17 18 import ( 19 "errors" 20 "fmt" 21 "go/build" 22 "log" 23 "path" 24 "strings" 25 26 "github.com/bazelbuild/bazel-gazelle/internal/config" 27 "github.com/bazelbuild/bazel-gazelle/internal/label" 28 "github.com/bazelbuild/bazel-gazelle/internal/language/proto" 29 "github.com/bazelbuild/bazel-gazelle/internal/pathtools" 30 "github.com/bazelbuild/bazel-gazelle/internal/repos" 31 "github.com/bazelbuild/bazel-gazelle/internal/resolve" 32 "github.com/bazelbuild/bazel-gazelle/internal/rule" 33 ) 34 35 func (_ *goLang) Imports(_ *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { 36 if !isGoLibrary(r.Kind()) { 37 return nil 38 } 39 if importPath := r.AttrString("importpath"); importPath == "" { 40 return []resolve.ImportSpec{} 41 } else { 42 return []resolve.ImportSpec{{goName, importPath}} 43 } 44 } 45 46 func (_ *goLang) Embeds(r *rule.Rule, from label.Label) []label.Label { 47 embedStrings := r.AttrStrings("embed") 48 if isGoProtoLibrary(r.Kind()) { 49 embedStrings = append(embedStrings, r.AttrString("proto")) 50 } 51 embedLabels := make([]label.Label, 0, len(embedStrings)) 52 for _, s := range embedStrings { 53 l, err := label.Parse(s) 54 if err != nil { 55 continue 56 } 57 l = l.Abs(from.Repo, from.Pkg) 58 embedLabels = append(embedLabels, l) 59 } 60 return embedLabels 61 } 62 63 func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r *rule.Rule, from label.Label) { 64 importsRaw := r.PrivateAttr(config.GazelleImportsKey) 65 if importsRaw == nil { 66 // may not be set in tests. 67 return 68 } 69 imports := importsRaw.(rule.PlatformStrings) 70 r.DelAttr("deps") 71 resolve := resolveGo 72 if r.Kind() == "go_proto_library" { 73 resolve = resolveProto 74 } 75 deps, errs := imports.Map(func(imp string) (string, error) { 76 l, err := resolve(c, ix, rc, r, imp, from) 77 if err == skipImportError { 78 return "", nil 79 } else if err != nil { 80 return "", err 81 } 82 for _, embed := range gl.Embeds(r, from) { 83 if embed.Equal(l) { 84 return "", nil 85 } 86 } 87 l = l.Rel(from.Repo, from.Pkg) 88 return l.String(), nil 89 }) 90 for _, err := range errs { 91 log.Print(err) 92 } 93 if !deps.IsEmpty() { 94 r.SetAttr("deps", deps) 95 } 96 } 97 98 var ( 99 skipImportError = errors.New("std or self import") 100 notFoundError = errors.New("rule not found") 101 ) 102 103 func resolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r *rule.Rule, imp string, from label.Label) (label.Label, error) { 104 gc := getGoConfig(c) 105 pc := proto.GetProtoConfig(c) 106 if build.IsLocalImport(imp) { 107 cleanRel := path.Clean(path.Join(from.Pkg, imp)) 108 if build.IsLocalImport(cleanRel) { 109 return label.NoLabel, fmt.Errorf("relative import path %q from %q points outside of repository", imp, from.Pkg) 110 } 111 imp = path.Join(gc.prefix, cleanRel) 112 } 113 114 if isStandard(imp) { 115 return label.NoLabel, skipImportError 116 } 117 118 if pc.Mode.ShouldUseKnownImports() { 119 // These are commonly used libraries that depend on Well Known Types. 120 // They depend on the generated versions of these protos to avoid conflicts. 121 // However, since protoc-gen-go depends on these libraries, we generate 122 // its rules in disable_global mode (to avoid cyclic dependency), so the 123 // "go_default_library" versions of these libraries depend on the 124 // pre-generated versions of the proto libraries. 125 switch imp { 126 case "github.com/golang/protobuf/jsonpb": 127 return label.New("com_github_golang_protobuf", "jsonpb", "go_default_library_gen"), nil 128 case "github.com/golang/protobuf/descriptor": 129 return label.New("com_github_golang_protobuf", "descriptor", "go_default_library_gen"), nil 130 case "github.com/golang/protobuf/ptypes": 131 return label.New("com_github_golang_protobuf", "ptypes", "go_default_library_gen"), nil 132 } 133 if l, ok := knownGoProtoImports[imp]; ok { 134 return l, nil 135 } 136 } 137 138 if l, err := resolveWithIndexGo(ix, imp, from); err == nil || err == skipImportError { 139 return l, err 140 } else if err != notFoundError { 141 return label.NoLabel, err 142 } 143 144 if pathtools.HasPrefix(imp, gc.prefix) { 145 pkg := path.Join(gc.prefixRel, pathtools.TrimPrefix(imp, gc.prefix)) 146 return label.New("", pkg, config.DefaultLibName), nil 147 } 148 149 if gc.depMode == externalMode { 150 return resolveExternal(rc, imp) 151 } else { 152 return resolveVendored(rc, imp) 153 } 154 } 155 156 // isStandard returns whether a package is in the standard library. 157 func isStandard(imp string) bool { 158 return stdPackages[imp] 159 } 160 161 func resolveWithIndexGo(ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) { 162 matches := ix.FindRulesByImport(resolve.ImportSpec{Lang: "go", Imp: imp}, "go") 163 var bestMatch resolve.FindResult 164 var bestMatchIsVendored bool 165 var bestMatchVendorRoot string 166 var matchError error 167 168 for _, m := range matches { 169 // Apply vendoring logic for Go libraries. A library in a vendor directory 170 // is only visible in the parent tree. Vendored libraries supercede 171 // non-vendored libraries, and libraries closer to from.Pkg supercede 172 // those further up the tree. 173 isVendored := false 174 vendorRoot := "" 175 parts := strings.Split(m.Label.Pkg, "/") 176 for i := len(parts) - 1; i >= 0; i-- { 177 if parts[i] == "vendor" { 178 isVendored = true 179 vendorRoot = strings.Join(parts[:i], "/") 180 break 181 } 182 } 183 if isVendored { 184 } 185 if isVendored && !label.New(m.Label.Repo, vendorRoot, "").Contains(from) { 186 // vendor directory not visible 187 continue 188 } 189 if bestMatch.Label.Equal(label.NoLabel) || isVendored && (!bestMatchIsVendored || len(vendorRoot) > len(bestMatchVendorRoot)) { 190 // Current match is better 191 bestMatch = m 192 bestMatchIsVendored = isVendored 193 bestMatchVendorRoot = vendorRoot 194 matchError = nil 195 } else if (!isVendored && bestMatchIsVendored) || (isVendored && len(vendorRoot) < len(bestMatchVendorRoot)) { 196 // Current match is worse 197 } else { 198 // Match is ambiguous 199 matchError = fmt.Errorf("multiple rules (%s and %s) may be imported with %q from %s", bestMatch.Label, m.Label, imp, from) 200 } 201 } 202 if matchError != nil { 203 return label.NoLabel, matchError 204 } 205 if bestMatch.Label.Equal(label.NoLabel) { 206 return label.NoLabel, notFoundError 207 } 208 if bestMatch.IsSelfImport(from) { 209 return label.NoLabel, skipImportError 210 } 211 return bestMatch.Label, nil 212 } 213 214 func resolveExternal(rc *repos.RemoteCache, imp string) (label.Label, error) { 215 prefix, repo, err := rc.Root(imp) 216 if err != nil { 217 return label.NoLabel, err 218 } 219 220 var pkg string 221 if imp != prefix { 222 pkg = pathtools.TrimPrefix(imp, prefix) 223 } 224 225 return label.New(repo, pkg, config.DefaultLibName), nil 226 } 227 228 func resolveVendored(rc *repos.RemoteCache, imp string) (label.Label, error) { 229 return label.New("", path.Join("vendor", imp), config.DefaultLibName), nil 230 } 231 232 func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r *rule.Rule, imp string, from label.Label) (label.Label, error) { 233 pc := proto.GetProtoConfig(c) 234 235 if wellKnownProtos[imp] { 236 return label.NoLabel, skipImportError 237 } 238 239 if l, ok := knownProtoImports[imp]; ok && pc.Mode.ShouldUseKnownImports() { 240 if l.Equal(from) { 241 return label.NoLabel, skipImportError 242 } else { 243 return l, nil 244 } 245 } 246 247 if l, err := resolveWithIndexProto(ix, imp, from); err == nil || err == skipImportError { 248 return l, err 249 } else if err != notFoundError { 250 return label.NoLabel, err 251 } 252 253 // As a fallback, guess the label based on the proto file name. We assume 254 // all proto files in a directory belong to the same package, and the 255 // package name matches the directory base name. We also assume that protos 256 // in the vendor directory must refer to something else in vendor. 257 rel := path.Dir(imp) 258 if rel == "." { 259 rel = "" 260 } 261 if from.Pkg == "vendor" || strings.HasPrefix(from.Pkg, "vendor/") { 262 rel = path.Join("vendor", rel) 263 } 264 return label.New("", rel, config.DefaultLibName), nil 265 } 266 267 // wellKnownProtos is the set of proto sets for which we don't need to add 268 // an explicit dependency in go_proto_library. 269 // TODO(jayconrod): generate from 270 // @io_bazel_rules_go//proto/wkt:WELL_KNOWN_TYPE_PACKAGES 271 var wellKnownProtos = map[string]bool{ 272 "google/protobuf/any.proto": true, 273 "google/protobuf/api.proto": true, 274 "google/protobuf/compiler_plugin.proto": true, 275 "google/protobuf/descriptor.proto": true, 276 "google/protobuf/duration.proto": true, 277 "google/protobuf/empty.proto": true, 278 "google/protobuf/field_mask.proto": true, 279 "google/protobuf/source_context.proto": true, 280 "google/protobuf/struct.proto": true, 281 "google/protobuf/timestamp.proto": true, 282 "google/protobuf/type.proto": true, 283 "google/protobuf/wrappers.proto": true, 284 } 285 286 func resolveWithIndexProto(ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) { 287 matches := ix.FindRulesByImport(resolve.ImportSpec{Lang: "proto", Imp: imp}, "go") 288 if len(matches) == 0 { 289 return label.NoLabel, notFoundError 290 } 291 if len(matches) > 1 { 292 return label.NoLabel, fmt.Errorf("multiple rules (%s and %s) may be imported with %q from %s", matches[0].Label, matches[1].Label, imp, from) 293 } 294 if matches[0].IsSelfImport(from) { 295 return label.NoLabel, skipImportError 296 } 297 return matches[0].Label, nil 298 } 299 300 func isGoLibrary(kind string) bool { 301 return kind == "go_library" || isGoProtoLibrary(kind) 302 } 303 304 func isGoProtoLibrary(kind string) bool { 305 return kind == "go_proto_library" || kind == "go_grpc_library" 306 }