github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/resolve/resolve.go (about)

     1  /* Copyright 2016 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 resolve
    17  
    18  import (
    19  	"fmt"
    20  	"go/build"
    21  	"log"
    22  	"path"
    23  	"strings"
    24  
    25  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    26  	"github.com/bazelbuild/bazel-gazelle/internal/label"
    27  	"github.com/bazelbuild/bazel-gazelle/internal/pathtools"
    28  	"github.com/bazelbuild/bazel-gazelle/internal/repos"
    29  	bf "github.com/bazelbuild/buildtools/build"
    30  )
    31  
    32  // Resolver resolves import strings in source files (import paths in Go,
    33  // import statements in protos) into Bazel labels.
    34  type Resolver struct {
    35  	c        *config.Config
    36  	l        *label.Labeler
    37  	ix       *RuleIndex
    38  	external nonlocalResolver
    39  }
    40  
    41  // nonlocalResolver resolves import paths outside of the current repository's
    42  // prefix. Once we have smarter import path resolution, this shouldn't
    43  // be necessary, and we can remove this abstraction.
    44  type nonlocalResolver interface {
    45  	resolve(imp string) (label.Label, error)
    46  }
    47  
    48  func NewResolver(c *config.Config, l *label.Labeler, ix *RuleIndex, rc *repos.RemoteCache) *Resolver {
    49  	var e nonlocalResolver
    50  	switch c.DepMode {
    51  	case config.ExternalMode:
    52  		e = newExternalResolver(l, rc)
    53  	case config.VendorMode:
    54  		e = newVendoredResolver(l)
    55  	}
    56  
    57  	return &Resolver{
    58  		c:        c,
    59  		l:        l,
    60  		ix:       ix,
    61  		external: e,
    62  	}
    63  }
    64  
    65  // ResolveRule copies and modifies a generated rule e by replacing the import
    66  // paths in the "_gazelle_imports" attribute with labels in a "deps"
    67  // attribute. This may be safely called on expressions that aren't Go rules
    68  // (the original expression will be returned). Any existing "deps" attribute
    69  // is deleted, so it may be necessary to merge the result.
    70  func (r *Resolver) ResolveRule(e bf.Expr, pkgRel string) bf.Expr {
    71  	call, ok := e.(*bf.CallExpr)
    72  	if !ok {
    73  		return e
    74  	}
    75  	rule := bf.Rule{Call: call}
    76  	from := label.New("", pkgRel, rule.Name())
    77  
    78  	var resolve func(imp string, from label.Label) (label.Label, error)
    79  	switch rule.Kind() {
    80  	case "go_library", "go_binary", "go_test":
    81  		resolve = r.resolveGo
    82  	case "proto_library":
    83  		resolve = r.resolveProto
    84  	case "go_proto_library", "go_grpc_library":
    85  		resolve = r.resolveGoProto
    86  	default:
    87  		return e
    88  	}
    89  
    90  	resolved := *call
    91  	resolved.List = append([]bf.Expr{}, call.List...)
    92  	rule.Call = &resolved
    93  
    94  	imports := rule.Attr(config.GazelleImportsKey)
    95  	rule.DelAttr(config.GazelleImportsKey)
    96  	rule.DelAttr("deps")
    97  	deps := mapExprStrings(imports, func(imp string) string {
    98  		label, err := resolve(imp, from)
    99  		if err != nil {
   100  			switch err.(type) {
   101  			case standardImportError, selfImportError:
   102  				return ""
   103  			default:
   104  				log.Print(err)
   105  				return ""
   106  			}
   107  		}
   108  		label.Relative = label.Repo == "" && label.Pkg == pkgRel
   109  		return label.String()
   110  	})
   111  	if deps != nil {
   112  		rule.SetAttr("deps", deps)
   113  	}
   114  
   115  	return &resolved
   116  }
   117  
   118  type standardImportError struct {
   119  	imp string
   120  }
   121  
   122  func (e standardImportError) Error() string {
   123  	return fmt.Sprintf("import path %q is in the standard library", e.imp)
   124  }
   125  
   126  // mapExprStrings applies a function f to the strings in e and returns a new
   127  // expression with the results. Scalar strings, lists, dicts, selects, and
   128  // concatenations are supported.
   129  func mapExprStrings(e bf.Expr, f func(string) string) bf.Expr {
   130  	if e == nil {
   131  		return nil
   132  	}
   133  	switch expr := e.(type) {
   134  	case *bf.StringExpr:
   135  		s := f(expr.Value)
   136  		if s == "" {
   137  			return nil
   138  		}
   139  		ret := *expr
   140  		ret.Value = s
   141  		return &ret
   142  
   143  	case *bf.ListExpr:
   144  		var list []bf.Expr
   145  		for _, elem := range expr.List {
   146  			elem = mapExprStrings(elem, f)
   147  			if elem != nil {
   148  				list = append(list, elem)
   149  			}
   150  		}
   151  		if len(list) == 0 && len(expr.List) > 0 {
   152  			return nil
   153  		}
   154  		ret := *expr
   155  		ret.List = list
   156  		return &ret
   157  
   158  	case *bf.DictExpr:
   159  		var cases []bf.Expr
   160  		isEmpty := true
   161  		for _, kv := range expr.List {
   162  			keyval, ok := kv.(*bf.KeyValueExpr)
   163  			if !ok {
   164  				log.Panicf("unexpected expression in generated imports dict: %#v", kv)
   165  			}
   166  			value := mapExprStrings(keyval.Value, f)
   167  			if value != nil {
   168  				cases = append(cases, &bf.KeyValueExpr{Key: keyval.Key, Value: value})
   169  				if key, ok := keyval.Key.(*bf.StringExpr); !ok || key.Value != "//conditions:default" {
   170  					isEmpty = false
   171  				}
   172  			}
   173  		}
   174  		if isEmpty {
   175  			return nil
   176  		}
   177  		ret := *expr
   178  		ret.List = cases
   179  		return &ret
   180  
   181  	case *bf.CallExpr:
   182  		if x, ok := expr.X.(*bf.LiteralExpr); !ok || x.Token != "select" || len(expr.List) != 1 {
   183  			log.Panicf("unexpected call expression in generated imports: %#v", e)
   184  		}
   185  		arg := mapExprStrings(expr.List[0], f)
   186  		if arg == nil {
   187  			return nil
   188  		}
   189  		call := *expr
   190  		call.List[0] = arg
   191  		return &call
   192  
   193  	case *bf.BinaryExpr:
   194  		x := mapExprStrings(expr.X, f)
   195  		y := mapExprStrings(expr.Y, f)
   196  		if x == nil {
   197  			return y
   198  		}
   199  		if y == nil {
   200  			return x
   201  		}
   202  		binop := *expr
   203  		binop.X = x
   204  		binop.Y = y
   205  		return &binop
   206  
   207  	default:
   208  		log.Panicf("unexpected expression in generated imports: %#v", e)
   209  		return nil
   210  	}
   211  }
   212  
   213  // resolveGo resolves an import path from a Go source file to a label.
   214  // pkgRel is the path to the Go package relative to the repository root; it
   215  // is used to resolve relative imports.
   216  func (r *Resolver) resolveGo(imp string, from label.Label) (label.Label, error) {
   217  	if build.IsLocalImport(imp) {
   218  		cleanRel := path.Clean(path.Join(from.Pkg, imp))
   219  		if build.IsLocalImport(cleanRel) {
   220  			return label.NoLabel, fmt.Errorf("relative import path %q from %q points outside of repository", imp, from.Pkg)
   221  		}
   222  		imp = path.Join(r.c.GoPrefix, cleanRel)
   223  	}
   224  
   225  	if IsStandard(imp) {
   226  		return label.NoLabel, standardImportError{imp}
   227  	}
   228  
   229  	if l, err := r.ix.findLabelByImport(importSpec{config.GoLang, imp}, config.GoLang, from); err != nil {
   230  		if _, ok := err.(ruleNotFoundError); !ok {
   231  			return label.NoLabel, err
   232  		}
   233  	} else {
   234  		return l, nil
   235  	}
   236  
   237  	if pathtools.HasPrefix(imp, r.c.GoPrefix) {
   238  		return r.l.LibraryLabel(pathtools.TrimPrefix(imp, r.c.GoPrefix)), nil
   239  	}
   240  
   241  	return r.external.resolve(imp)
   242  }
   243  
   244  const (
   245  	wellKnownPrefix     = "google/protobuf/"
   246  	wellKnownGoProtoPkg = "ptypes"
   247  	descriptorPkg       = "protoc-gen-go/descriptor"
   248  )
   249  
   250  // resolveProto resolves an import statement in a .proto file to a label
   251  // for a proto_library rule.
   252  func (r *Resolver) resolveProto(imp string, from label.Label) (label.Label, error) {
   253  	if !strings.HasSuffix(imp, ".proto") {
   254  		return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp)
   255  	}
   256  	if isWellKnown(imp) {
   257  		name := path.Base(imp[:len(imp)-len(".proto")]) + "_proto"
   258  		return label.New(config.WellKnownTypesProtoRepo, "", name), nil
   259  	}
   260  
   261  	if l, err := r.ix.findLabelByImport(importSpec{config.ProtoLang, imp}, config.ProtoLang, from); err != nil {
   262  		if _, ok := err.(ruleNotFoundError); !ok {
   263  			return label.NoLabel, err
   264  		}
   265  	} else {
   266  		return l, nil
   267  	}
   268  
   269  	rel := path.Dir(imp)
   270  	if rel == "." {
   271  		rel = ""
   272  	}
   273  	name := pathtools.RelBaseName(rel, r.c.GoPrefix, r.c.RepoRoot)
   274  	return r.l.ProtoLabel(rel, name), nil
   275  }
   276  
   277  // resolveGoProto resolves an import statement in a .proto file to a
   278  // label for a go_library rule that embeds the corresponding go_proto_library.
   279  func (r *Resolver) resolveGoProto(imp string, from label.Label) (label.Label, error) {
   280  	if !strings.HasSuffix(imp, ".proto") {
   281  		return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp)
   282  	}
   283  	stem := imp[:len(imp)-len(".proto")]
   284  
   285  	if isWellKnown(stem) {
   286  		return label.NoLabel, standardImportError{imp}
   287  	}
   288  
   289  	if l, err := r.ix.findLabelByImport(importSpec{config.ProtoLang, imp}, config.GoLang, from); err != nil {
   290  		if _, ok := err.(ruleNotFoundError); !ok {
   291  			return label.NoLabel, err
   292  		}
   293  	} else {
   294  		return l, err
   295  	}
   296  
   297  	// As a fallback, guess the label based on the proto file name. We assume
   298  	// all proto files in a directory belong to the same package, and the
   299  	// package name matches the directory base name. We also assume that protos
   300  	// in the vendor directory must refer to something else in vendor.
   301  	rel := path.Dir(imp)
   302  	if rel == "." {
   303  		rel = ""
   304  	}
   305  	if from.Pkg == "vendor" || strings.HasPrefix(from.Pkg, "vendor/") {
   306  		rel = path.Join("vendor", rel)
   307  	}
   308  	return r.l.LibraryLabel(rel), nil
   309  }
   310  
   311  // IsStandard returns whether a package is in the standard library.
   312  func IsStandard(imp string) bool {
   313  	return stdPackages[imp]
   314  }
   315  
   316  func isWellKnown(imp string) bool {
   317  	return strings.HasPrefix(imp, wellKnownPrefix) && strings.TrimPrefix(imp, wellKnownPrefix) == path.Base(imp)
   318  }