github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/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/config" 27 "github.com/bazelbuild/bazel-gazelle/label" 28 "github.com/bazelbuild/bazel-gazelle/pathtools" 29 "github.com/bazelbuild/bazel-gazelle/repo" 30 "github.com/bazelbuild/bazel-gazelle/resolve" 31 "github.com/bazelbuild/bazel-gazelle/rule" 32 ) 33 34 func (*goLang) Imports(_ *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { 35 if !isGoLibrary(r.Kind()) || isExtraLibrary(r) { 36 return nil 37 } 38 if importPath := r.AttrString("importpath"); importPath == "" { 39 return []resolve.ImportSpec{} 40 } else { 41 return []resolve.ImportSpec{{ 42 Lang: goName, 43 Imp: importPath, 44 }} 45 } 46 } 47 48 func (*goLang) Embeds(r *rule.Rule, from label.Label) []label.Label { 49 embedStrings := r.AttrStrings("embed") 50 if isGoProtoLibrary(r.Kind()) { 51 embedStrings = append(embedStrings, r.AttrString("proto")) 52 } 53 embedLabels := make([]label.Label, 0, len(embedStrings)) 54 for _, s := range embedStrings { 55 l, err := label.Parse(s) 56 if err != nil { 57 continue 58 } 59 l = l.Abs(from.Repo, from.Pkg) 60 embedLabels = append(embedLabels, l) 61 } 62 return embedLabels 63 } 64 65 func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, importsRaw interface{}, from label.Label) { 66 if importsRaw == nil { 67 // may not be set in tests. 68 return 69 } 70 imports := importsRaw.(rule.PlatformStrings) 71 r.DelAttr("deps") 72 var resolve func(*config.Config, *resolve.RuleIndex, *repo.RemoteCache, string, label.Label) (label.Label, error) 73 switch r.Kind() { 74 case "go_proto_library": 75 resolve = resolveProto 76 default: 77 resolve = ResolveGo 78 } 79 deps, errs := imports.Map(func(imp string) (string, error) { 80 l, err := resolve(c, ix, rc, imp, from) 81 if err == errSkipImport { 82 return "", nil 83 } else if err != nil { 84 return "", err 85 } 86 for _, embed := range gl.Embeds(r, from) { 87 if embed.Equal(l) { 88 return "", nil 89 } 90 } 91 l = l.Rel(from.Repo, from.Pkg) 92 return l.String(), nil 93 }) 94 for _, err := range errs { 95 log.Print(err) 96 } 97 if !deps.IsEmpty() { 98 if r.Kind() == "go_proto_library" { 99 // protos may import the same library multiple times by different names, 100 // so we need to de-duplicate them. Protos are not platform-specific, 101 // so it's safe to just flatten them. 102 r.SetAttr("deps", deps.Flat()) 103 } else { 104 r.SetAttr("deps", deps) 105 } 106 } 107 } 108 109 var ( 110 errSkipImport = errors.New("std or self import") 111 errNotFound = errors.New("rule not found") 112 ) 113 114 // ResolveGo resolves a Go import path to a Bazel label, possibly using the 115 // given rule index and remote cache. Some special cases may be applied to 116 // known proto import paths, depending on the current proto mode. 117 // 118 // This may be used directly by other language extensions related to Go 119 // (gomock). Gazelle calls Language.Resolve instead. 120 func ResolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, imp string, from label.Label) (label.Label, error) { 121 gc := getGoConfig(c) 122 if build.IsLocalImport(imp) { 123 cleanRel := path.Clean(path.Join(from.Pkg, imp)) 124 if build.IsLocalImport(cleanRel) { 125 return label.NoLabel, fmt.Errorf("relative import path %q from %q points outside of repository", imp, from.Pkg) 126 } 127 imp = path.Join(gc.prefix, cleanRel) 128 } 129 130 if IsStandard(imp) { 131 return label.NoLabel, errSkipImport 132 } 133 134 if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "go", Imp: imp}, "go"); ok { 135 return l, nil 136 } 137 138 if l, err := resolveWithIndexGo(c, ix, imp, from); err == nil || err == errSkipImport { 139 return l, err 140 } else if err != errNotFound { 141 return label.NoLabel, err 142 } 143 144 // Special cases for rules_go and bazel_gazelle. 145 // These have names that don't following conventions and they're 146 // typeically declared with http_archive, not go_repository, so Gazelle 147 // won't recognize them. 148 if !c.Bzlmod { 149 if pathtools.HasPrefix(imp, "github.com/bazelbuild/rules_go") { 150 pkg := pathtools.TrimPrefix(imp, "github.com/bazelbuild/rules_go") 151 return label.New("io_bazel_rules_go", pkg, "go_default_library"), nil 152 } else if pathtools.HasPrefix(imp, "github.com/bazelbuild/bazel-gazelle") { 153 pkg := pathtools.TrimPrefix(imp, "github.com/bazelbuild/bazel-gazelle") 154 return label.New("bazel_gazelle", pkg, "go_default_library"), nil 155 } 156 } 157 158 if !c.IndexLibraries { 159 // packages in current repo were not indexed, relying on prefix to decide what may have been in 160 // current repo 161 if pathtools.HasPrefix(imp, gc.prefix) { 162 pkg := path.Join(gc.prefixRel, pathtools.TrimPrefix(imp, gc.prefix)) 163 libName := libNameByConvention(gc.goNamingConvention, imp, "") 164 return label.New("", pkg, libName), nil 165 } 166 } 167 168 if gc.depMode == vendorMode { 169 return resolveVendored(gc, imp) 170 } 171 var resolveFn func(string) (string, string, error) 172 if gc.depMode == staticMode { 173 resolveFn = rc.RootStatic 174 } else if gc.moduleMode || pathWithoutSemver(imp) != "" { 175 resolveFn = rc.Mod 176 } else { 177 resolveFn = rc.Root 178 } 179 return resolveToExternalLabel(c, resolveFn, imp) 180 } 181 182 // IsStandard returns whether a package is in the standard library. 183 func IsStandard(imp string) bool { 184 return stdPackages[imp] 185 } 186 187 func resolveWithIndexGo(c *config.Config, ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) { 188 matches := ix.FindRulesByImportWithConfig(c, resolve.ImportSpec{Lang: "go", Imp: imp}, "go") 189 var bestMatch resolve.FindResult 190 var bestMatchIsVendored bool 191 var bestMatchVendorRoot string 192 var bestMatchEmbedsProtos bool 193 var matchError error 194 goRepositoryMode := getGoConfig(c).goRepositoryMode 195 196 for _, m := range matches { 197 // Apply vendoring logic for Go libraries. A library in a vendor directory 198 // is only visible in the parent tree. Vendored libraries supercede 199 // non-vendored libraries, and libraries closer to from.Pkg supercede 200 // those further up the tree. 201 // 202 // Also, in external repositories, prefer go_proto_library targets to checked-in .go files 203 // pregenerated from .proto files over go_proto_library targets. Ideally, the two should be 204 // in sync. If not, users can choose between the two by using the go_generate_proto 205 // directive. 206 isVendored := false 207 vendorRoot := "" 208 parts := strings.Split(m.Label.Pkg, "/") 209 for i := len(parts) - 1; i >= 0; i-- { 210 if parts[i] == "vendor" { 211 isVendored = true 212 vendorRoot = strings.Join(parts[:i], "/") 213 break 214 } 215 } 216 if isVendored && !label.New(m.Label.Repo, vendorRoot, "").Contains(from) { 217 // vendor directory not visible 218 continue 219 } 220 221 embedsProtos := false 222 for _, embed := range m.Embeds { 223 if strings.HasSuffix(embed.Name, goProtoSuffix) { 224 embedsProtos = true 225 } 226 } 227 228 if bestMatch.Label.Equal(label.NoLabel) || 229 (isVendored && (!bestMatchIsVendored || len(vendorRoot) > len(bestMatchVendorRoot))) || 230 (goRepositoryMode && !bestMatchEmbedsProtos && embedsProtos) { 231 // Current match is better 232 bestMatch = m 233 bestMatchIsVendored = isVendored 234 bestMatchVendorRoot = vendorRoot 235 bestMatchEmbedsProtos = embedsProtos 236 matchError = nil 237 } else if (!isVendored && bestMatchIsVendored) || 238 (isVendored && len(vendorRoot) < len(bestMatchVendorRoot)) || 239 (goRepositoryMode && bestMatchEmbedsProtos && !embedsProtos) { 240 // Current match is worse 241 } else { 242 // Match is ambiguous 243 // TODO: consider listing all the ambiguous rules here. 244 matchError = fmt.Errorf("rule %s imports %q which matches multiple rules: %s and %s. # gazelle:resolve may be used to disambiguate", from, imp, bestMatch.Label, m.Label) 245 } 246 } 247 if matchError != nil { 248 return label.NoLabel, matchError 249 } 250 if bestMatch.Label.Equal(label.NoLabel) { 251 return label.NoLabel, errNotFound 252 } 253 if bestMatch.IsSelfImport(from) { 254 return label.NoLabel, errSkipImport 255 } 256 return bestMatch.Label, nil 257 } 258 259 func resolveToExternalLabel(c *config.Config, resolveFn func(string) (string, string, error), imp string) (label.Label, error) { 260 prefix, repo, err := resolveFn(imp) 261 if err != nil { 262 return label.NoLabel, err 263 } else if prefix == "" && repo == "" { 264 return label.NoLabel, errSkipImport 265 } 266 267 var pkg string 268 if pathtools.HasPrefix(imp, prefix) { 269 pkg = pathtools.TrimPrefix(imp, prefix) 270 } else if impWithoutSemver := pathWithoutSemver(imp); pathtools.HasPrefix(impWithoutSemver, prefix) { 271 // We may have used minimal module compatibility to resolve a path 272 // without a semantic import version suffix to a repository that has one. 273 pkg = pathtools.TrimPrefix(impWithoutSemver, prefix) 274 } 275 276 // Determine what naming convention is used by the repository. 277 // If there is no known repository, it's probably declared in an http_archive 278 // somewhere like go_rules_dependencies, so use the old naming convention, 279 // unless the user has explicitly told us otherwise. 280 // If the repository uses the import_alias convention (default for 281 // go_repository), use the convention from the current directory unless the 282 // user has told us otherwise. 283 gc := getGoConfig(c) 284 nc := gc.repoNamingConvention[repo] 285 if nc == unknownNamingConvention { 286 if gc.goNamingConventionExternal != unknownNamingConvention { 287 nc = gc.goNamingConventionExternal 288 } else { 289 nc = goDefaultLibraryNamingConvention 290 } 291 } else if nc == importAliasNamingConvention { 292 if gc.goNamingConventionExternal != unknownNamingConvention { 293 nc = gc.goNamingConventionExternal 294 } else { 295 nc = gc.goNamingConvention 296 } 297 } 298 299 name := libNameByConvention(nc, imp, "") 300 return label.New(repo, pkg, name), nil 301 } 302 303 func resolveVendored(gc *goConfig, imp string) (label.Label, error) { 304 name := libNameByConvention(gc.goNamingConvention, imp, "") 305 return label.New("", path.Join("vendor", imp), name), nil 306 } 307 308 func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, imp string, from label.Label) (label.Label, error) { 309 if wellKnownProtos[imp] { 310 return label.NoLabel, errSkipImport 311 } 312 313 if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "go"); ok { 314 return l, nil 315 } 316 317 if l, err := resolveWithIndexProto(c, ix, imp, from); err == nil || err == errSkipImport { 318 return l, err 319 } else if err != errNotFound { 320 return label.NoLabel, err 321 } 322 323 // As a fallback, guess the label based on the proto file name. We assume 324 // all proto files in a directory belong to the same package, and the 325 // package name matches the directory base name. We also assume that protos 326 // in the vendor directory must refer to something else in vendor. 327 rel := path.Dir(imp) 328 if rel == "." { 329 rel = "" 330 } 331 if from.Pkg == "vendor" || strings.HasPrefix(from.Pkg, "vendor/") { 332 rel = path.Join("vendor", rel) 333 } 334 libName := libNameByConvention(getGoConfig(c).goNamingConvention, imp, "") 335 return label.New("", rel, libName), nil 336 } 337 338 // wellKnownProtos is the set of proto sets for which we don't need to add 339 // an explicit dependency in go_proto_library. 340 // TODO(jayconrod): generate from 341 // @io_bazel_rules_go//proto/wkt:WELL_KNOWN_TYPE_PACKAGES 342 var wellKnownProtos = map[string]bool{ 343 "google/protobuf/any.proto": true, 344 "google/protobuf/api.proto": true, 345 "google/protobuf/compiler/plugin.proto": true, 346 "google/protobuf/descriptor.proto": true, 347 "google/protobuf/duration.proto": true, 348 "google/protobuf/empty.proto": true, 349 "google/protobuf/field_mask.proto": true, 350 "google/protobuf/source_context.proto": true, 351 "google/protobuf/struct.proto": true, 352 "google/protobuf/timestamp.proto": true, 353 "google/protobuf/type.proto": true, 354 "google/protobuf/wrappers.proto": true, 355 } 356 357 func resolveWithIndexProto(c *config.Config, ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) { 358 matches := ix.FindRulesByImportWithConfig(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "go") 359 if len(matches) == 0 { 360 return label.NoLabel, errNotFound 361 } 362 if len(matches) > 1 { 363 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) 364 } 365 if matches[0].IsSelfImport(from) { 366 return label.NoLabel, errSkipImport 367 } 368 return matches[0].Label, nil 369 } 370 371 func isGoLibrary(kind string) bool { 372 return kind == "go_library" || isGoProtoLibrary(kind) 373 } 374 375 func isGoProtoLibrary(kind string) bool { 376 return kind == "go_proto_library" || kind == "go_grpc_library" 377 } 378 379 // isExtraLibrary returns true if this rule is one of a handful of proto 380 // libraries generated by maybeGenerateExtraLib. It should not be indexed for 381 // dependency resolution. 382 func isExtraLibrary(r *rule.Rule) bool { 383 if !strings.HasSuffix(r.Name(), "_gen") { 384 return false 385 } 386 switch r.AttrString("importpath") { 387 case "github.com/golang/protobuf/descriptor", 388 "github.com/golang/protobuf/protoc-gen-go/generator", 389 "github.com/golang/protobuf/ptypes": 390 return true 391 default: 392 return false 393 } 394 }