github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/proto/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 proto 17 18 import ( 19 "errors" 20 "fmt" 21 "log" 22 "path" 23 "sort" 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 (*protoLang) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { 35 rel := f.Pkg 36 srcs := r.AttrStrings("srcs") 37 imports := make([]resolve.ImportSpec, len(srcs)) 38 pc := GetProtoConfig(c) 39 prefix := rel 40 if stripImportPrefix := r.AttrString("strip_import_prefix"); stripImportPrefix != "" { 41 // If strip_import_prefix starts with a /, it's interpreted as being 42 // relative to the repository root. Otherwise, it's interpreted as being 43 // relative to the package directory. 44 // 45 // So for the file //a/b:c/d.proto, if strip_import_prefix = "/a", 46 // the proto should be imported as "b/c/d.proto". 47 // If strip_import_prefix = "c", the proto should be imported as "d.proto". 48 // 49 // The package-relativeform only seems useful if there is one Bazel package 50 // covering protos in subdirectories. Gazelle does not generate build files 51 // like that, but we might still index proto_library rules like that, 52 // so we support it here. 53 if strings.HasPrefix(stripImportPrefix, "/") { 54 prefix = pathtools.TrimPrefix(rel, stripImportPrefix[len("/"):]) 55 } else { 56 prefix = pathtools.TrimPrefix(rel, path.Join(rel, pc.StripImportPrefix)) 57 } 58 if rel == prefix { 59 // Stripped prefix is not a prefix of rel, so the rule won't be buildable. 60 // Don't index it. 61 return nil 62 } 63 } 64 if importPrefix := r.AttrString("import_prefix"); importPrefix != "" { 65 prefix = path.Join(importPrefix, prefix) 66 } 67 for i, src := range srcs { 68 imports[i] = resolve.ImportSpec{Lang: "proto", Imp: path.Join(prefix, src)} 69 } 70 return imports 71 } 72 73 func (*protoLang) Embeds(r *rule.Rule, from label.Label) []label.Label { 74 return nil 75 } 76 77 func (*protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, importsRaw interface{}, from label.Label) { 78 if importsRaw == nil { 79 // may not be set in tests. 80 return 81 } 82 imports := importsRaw.([]string) 83 r.DelAttr("deps") 84 depSet := make(map[string]bool) 85 for _, imp := range imports { 86 l, err := resolveProto(c, ix, r, imp, from) 87 if err == errSkipImport { 88 continue 89 } else if err != nil { 90 log.Print(err) 91 } else { 92 l = l.Rel(from.Repo, from.Pkg) 93 depSet[l.String()] = true 94 } 95 } 96 if len(depSet) > 0 { 97 deps := make([]string, 0, len(depSet)) 98 for dep := range depSet { 99 deps = append(deps, dep) 100 } 101 sort.Strings(deps) 102 r.SetAttr("deps", deps) 103 } 104 } 105 106 var ( 107 errSkipImport = errors.New("std import") 108 errNotFound = errors.New("not found") 109 ) 110 111 func resolveProto(c *config.Config, ix *resolve.RuleIndex, r *rule.Rule, imp string, from label.Label) (label.Label, error) { 112 pc := GetProtoConfig(c) 113 if !strings.HasSuffix(imp, ".proto") { 114 return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp) 115 } 116 117 if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Imp: imp, Lang: "proto"}, "proto"); ok { 118 return l, nil 119 } 120 121 if l, ok := knownImports[imp]; ok && pc.Mode.ShouldUseKnownImports() { 122 if l.Equal(from) { 123 return label.NoLabel, errSkipImport 124 } else { 125 if workspaceName, isModule := bazelModuleRepos[l.Repo]; isModule { 126 apparentRepoName := c.ModuleToApparentName(l.Repo) 127 if apparentRepoName == "" { 128 // The user doesn't have a bazel_dep for the module containing this known 129 // target. 130 // TODO: Fail here instead when not using WORKSPACE anymore. 131 l.Repo = workspaceName 132 } else { 133 l.Repo = apparentRepoName 134 } 135 } 136 return l, nil 137 } 138 } 139 140 if l, err := resolveWithIndex(c, ix, imp, from); err == nil || err == errSkipImport { 141 return l, err 142 } else if err != errNotFound { 143 return label.NoLabel, err 144 } 145 146 rel := path.Dir(imp) 147 if rel == "." { 148 rel = "" 149 } 150 name := RuleName(rel) 151 return label.New("", rel, name), nil 152 } 153 154 func resolveWithIndex(c *config.Config, ix *resolve.RuleIndex, imp string, from label.Label) (label.Label, error) { 155 matches := ix.FindRulesByImportWithConfig(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "proto") 156 if len(matches) == 0 { 157 return label.NoLabel, errNotFound 158 } 159 if len(matches) > 1 { 160 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) 161 } 162 if matches[0].IsSelfImport(from) { 163 return label.NoLabel, errSkipImport 164 } 165 return matches[0].Label, nil 166 } 167 168 // CrossResolve provides dependency resolution logic for the go language extension. 169 func (*protoLang) CrossResolve(c *config.Config, ix *resolve.RuleIndex, imp resolve.ImportSpec, lang string) []resolve.FindResult { 170 if lang != "go" { 171 return nil 172 } 173 pc := GetProtoConfig(c) 174 if imp.Lang == "proto" && pc.Mode.ShouldUseKnownImports() { 175 if l, ok := knownProtoImports[imp.Imp]; ok { 176 return []resolve.FindResult{{Label: l}} 177 } 178 } 179 return nil 180 }