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  }