github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/config.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  	"flag"
    20  	"fmt"
    21  	"go/build"
    22  	"log"
    23  	"path"
    24  	"strings"
    25  
    26  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    27  	gzflag "github.com/bazelbuild/bazel-gazelle/internal/flag"
    28  	"github.com/bazelbuild/bazel-gazelle/internal/language/proto"
    29  	"github.com/bazelbuild/bazel-gazelle/internal/rule"
    30  	bzl "github.com/bazelbuild/buildtools/build"
    31  )
    32  
    33  // goConfig contains configuration values related to Go rules.
    34  type goConfig struct {
    35  	// genericTags is a set of tags that Gazelle considers to be true. Set with
    36  	// -build_tags or # gazelle:build_tags. Some tags, like gc, are always on.
    37  	genericTags map[string]bool
    38  
    39  	// prefix is a prefix of an import path, used to generate importpath
    40  	// attributes. Set with -go_prefix or # gazelle:prefix.
    41  	prefix string
    42  
    43  	// prefixRel is the package name of the directory where the prefix was set
    44  	// ("" for the root directory).
    45  	prefixRel string
    46  
    47  	// prefixSet indicates whether the prefix was set explicitly. It is an error
    48  	// to infer an importpath for a rule without setting the prefix.
    49  	prefixSet bool
    50  
    51  	// importMapPrefix is a prefix of a package path, used to generate importmap
    52  	// attributes. Set with # gazelle:importmap_prefix.
    53  	importMapPrefix string
    54  
    55  	// importMapPrefixRel is the package name of the directory where importMapPrefix
    56  	// was set ("" for the root directory).
    57  	importMapPrefixRel string
    58  
    59  	// depMode determines how imports that are not standard, indexed, or local
    60  	// (under the current prefix) should be resolved.
    61  	depMode dependencyMode
    62  }
    63  
    64  func newGoConfig() *goConfig {
    65  	gc := &goConfig{}
    66  	gc.preprocessTags()
    67  	return gc
    68  }
    69  
    70  func getGoConfig(c *config.Config) *goConfig {
    71  	return c.Exts[goName].(*goConfig)
    72  }
    73  
    74  func (gc *goConfig) clone() *goConfig {
    75  	gcCopy := *gc
    76  	gcCopy.genericTags = make(map[string]bool)
    77  	for k, v := range gc.genericTags {
    78  		gcCopy.genericTags[k] = v
    79  	}
    80  	return &gcCopy
    81  }
    82  
    83  // preprocessTags adds some tags which are on by default before they are
    84  // used to match files.
    85  func (gc *goConfig) preprocessTags() {
    86  	if gc.genericTags == nil {
    87  		gc.genericTags = make(map[string]bool)
    88  	}
    89  	gc.genericTags["gc"] = true
    90  }
    91  
    92  // setBuildTags sets genericTags by parsing as a comma separated list. An
    93  // error will be returned for tags that wouldn't be recognized by "go build".
    94  // preprocessTags should be called before this.
    95  func (gc *goConfig) setBuildTags(tags string) error {
    96  	if tags == "" {
    97  		return nil
    98  	}
    99  	for _, t := range strings.Split(tags, ",") {
   100  		if strings.HasPrefix(t, "!") {
   101  			return fmt.Errorf("build tags can't be negated: %s", t)
   102  		}
   103  		gc.genericTags[t] = true
   104  	}
   105  	return nil
   106  }
   107  
   108  // dependencyMode determines how imports of packages outside of the prefix
   109  // are resolved.
   110  type dependencyMode int
   111  
   112  const (
   113  	// externalMode indicates imports should be resolved to external dependencies
   114  	// (declared in WORKSPACE).
   115  	externalMode dependencyMode = iota
   116  
   117  	// vendorMode indicates imports should be resolved to libraries in the
   118  	// vendor directory.
   119  	vendorMode
   120  )
   121  
   122  func (m dependencyMode) String() string {
   123  	if m == externalMode {
   124  		return "external"
   125  	} else {
   126  		return "vendored"
   127  	}
   128  }
   129  
   130  type externalFlag struct {
   131  	depMode *dependencyMode
   132  }
   133  
   134  func (f *externalFlag) Set(value string) error {
   135  	switch value {
   136  	case "external":
   137  		*f.depMode = externalMode
   138  	case "vendored":
   139  		*f.depMode = vendorMode
   140  	default:
   141  		return fmt.Errorf("unrecognized dependency mode: %q", value)
   142  	}
   143  	return nil
   144  }
   145  
   146  func (f *externalFlag) String() string {
   147  	if f == nil || f.depMode == nil {
   148  		return "external"
   149  	}
   150  	return f.depMode.String()
   151  }
   152  
   153  type tagsFlag func(string) error
   154  
   155  func (f tagsFlag) Set(value string) error {
   156  	return f(value)
   157  }
   158  
   159  func (f tagsFlag) String() string {
   160  	return ""
   161  }
   162  
   163  func (_ *goLang) KnownDirectives() []string {
   164  	return []string{
   165  		"build_tags",
   166  		"importmap_prefix",
   167  		"prefix",
   168  	}
   169  }
   170  
   171  func (_ *goLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
   172  	gc := newGoConfig()
   173  	switch cmd {
   174  	case "fix", "update":
   175  		fs.Var(
   176  			tagsFlag(gc.setBuildTags),
   177  			"build_tags",
   178  			"comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.")
   179  		fs.Var(
   180  			&gzflag.ExplicitFlag{Value: &gc.prefix, IsSet: &gc.prefixSet},
   181  			"go_prefix",
   182  			"prefix of import paths in the current workspace")
   183  		fs.Var(
   184  			&externalFlag{&gc.depMode},
   185  			"external",
   186  			"external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/")
   187  	}
   188  	c.Exts[goName] = gc
   189  }
   190  
   191  func (_ *goLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
   192  	// The base of the -go_prefix flag may be used to generate proto_library
   193  	// rule names when there are no .proto sources (empty rules to be deleted)
   194  	// or when the package name can't be determined.
   195  	// TODO(jayconrod): deprecate and remove this behavior.
   196  	gc := getGoConfig(c)
   197  	pc := proto.GetProtoConfig(c)
   198  	pc.GoPrefix = gc.prefix
   199  	return nil
   200  }
   201  
   202  func (_ *goLang) Configure(c *config.Config, rel string, f *rule.File) {
   203  	var gc *goConfig
   204  	if raw, ok := c.Exts[goName]; !ok {
   205  		gc = newGoConfig()
   206  	} else {
   207  		gc = raw.(*goConfig).clone()
   208  	}
   209  	c.Exts[goName] = gc
   210  
   211  	if path.Base(rel) == "vendor" {
   212  		gc.importMapPrefix = inferImportPath(gc, rel)
   213  		gc.importMapPrefixRel = rel
   214  		gc.prefix = ""
   215  		gc.prefixRel = rel
   216  	}
   217  
   218  	if f != nil {
   219  		setPrefix := func(prefix string) {
   220  			if err := checkPrefix(prefix); err != nil {
   221  				log.Print(err)
   222  				return
   223  			}
   224  			gc.prefix = prefix
   225  			gc.prefixSet = true
   226  			gc.prefixRel = rel
   227  		}
   228  		for _, d := range f.Directives {
   229  			switch d.Key {
   230  			case "build_tags":
   231  				if err := gc.setBuildTags(d.Value); err != nil {
   232  					log.Print(err)
   233  					continue
   234  				}
   235  				gc.preprocessTags()
   236  				gc.setBuildTags(d.Value)
   237  			case "importmap_prefix":
   238  				gc.importMapPrefix = d.Value
   239  				gc.importMapPrefixRel = rel
   240  			case "prefix":
   241  				setPrefix(d.Value)
   242  			}
   243  		}
   244  		if !gc.prefixSet {
   245  			for _, r := range f.Rules {
   246  				switch r.Kind() {
   247  				case "go_prefix":
   248  					args := r.Args()
   249  					if len(args) != 1 {
   250  						continue
   251  					}
   252  					s, ok := args[0].(*bzl.StringExpr)
   253  					if !ok {
   254  						continue
   255  					}
   256  					setPrefix(s.Value)
   257  
   258  				case "gazelle":
   259  					if prefix := r.AttrString("prefix"); prefix != "" {
   260  						setPrefix(prefix)
   261  					}
   262  				}
   263  			}
   264  		}
   265  	}
   266  }
   267  
   268  // checkPrefix checks that a string may be used as a prefix. We forbid local
   269  // (relative) imports and those beginning with "/". We allow the empty string,
   270  // but generated rules must not have an empty importpath.
   271  func checkPrefix(prefix string) error {
   272  	if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) {
   273  		return fmt.Errorf("invalid prefix: %q", prefix)
   274  	}
   275  	return nil
   276  }