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  }