123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- /* Copyright 2018 The Bazel Authors. All rights reserved.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package golang
- import (
- "flag"
- "fmt"
- "go/build"
- "log"
- "path"
- "strings"
- "github.com/bazelbuild/bazel-gazelle/internal/config"
- gzflag "github.com/bazelbuild/bazel-gazelle/internal/flag"
- "github.com/bazelbuild/bazel-gazelle/internal/language/proto"
- "github.com/bazelbuild/bazel-gazelle/internal/rule"
- bzl "github.com/bazelbuild/buildtools/build"
- )
- // goConfig contains configuration values related to Go rules.
- type goConfig struct {
- // genericTags is a set of tags that Gazelle considers to be true. Set with
- // -build_tags or # gazelle:build_tags. Some tags, like gc, are always on.
- genericTags map[string]bool
- // prefix is a prefix of an import path, used to generate importpath
- // attributes. Set with -go_prefix or # gazelle:prefix.
- prefix string
- // prefixRel is the package name of the directory where the prefix was set
- // ("" for the root directory).
- prefixRel string
- // prefixSet indicates whether the prefix was set explicitly. It is an error
- // to infer an importpath for a rule without setting the prefix.
- prefixSet bool
- // importMapPrefix is a prefix of a package path, used to generate importmap
- // attributes. Set with # gazelle:importmap_prefix.
- importMapPrefix string
- // importMapPrefixRel is the package name of the directory where importMapPrefix
- // was set ("" for the root directory).
- importMapPrefixRel string
- // depMode determines how imports that are not standard, indexed, or local
- // (under the current prefix) should be resolved.
- depMode dependencyMode
- }
- func newGoConfig() *goConfig {
- gc := &goConfig{}
- gc.preprocessTags()
- return gc
- }
- func getGoConfig(c *config.Config) *goConfig {
- return c.Exts[goName].(*goConfig)
- }
- func (gc *goConfig) clone() *goConfig {
- gcCopy := *gc
- gcCopy.genericTags = make(map[string]bool)
- for k, v := range gc.genericTags {
- gcCopy.genericTags[k] = v
- }
- return &gcCopy
- }
- // preprocessTags adds some tags which are on by default before they are
- // used to match files.
- func (gc *goConfig) preprocessTags() {
- if gc.genericTags == nil {
- gc.genericTags = make(map[string]bool)
- }
- gc.genericTags["gc"] = true
- }
- // setBuildTags sets genericTags by parsing as a comma separated list. An
- // error will be returned for tags that wouldn't be recognized by "go build".
- // preprocessTags should be called before this.
- func (gc *goConfig) setBuildTags(tags string) error {
- if tags == "" {
- return nil
- }
- for _, t := range strings.Split(tags, ",") {
- if strings.HasPrefix(t, "!") {
- return fmt.Errorf("build tags can't be negated: %s", t)
- }
- gc.genericTags[t] = true
- }
- return nil
- }
- // dependencyMode determines how imports of packages outside of the prefix
- // are resolved.
- type dependencyMode int
- const (
- // externalMode indicates imports should be resolved to external dependencies
- // (declared in WORKSPACE).
- externalMode dependencyMode = iota
- // vendorMode indicates imports should be resolved to libraries in the
- // vendor directory.
- vendorMode
- )
- func (m dependencyMode) String() string {
- if m == externalMode {
- return "external"
- } else {
- return "vendored"
- }
- }
- type externalFlag struct {
- depMode *dependencyMode
- }
- func (f *externalFlag) Set(value string) error {
- switch value {
- case "external":
- *f.depMode = externalMode
- case "vendored":
- *f.depMode = vendorMode
- default:
- return fmt.Errorf("unrecognized dependency mode: %q", value)
- }
- return nil
- }
- func (f *externalFlag) String() string {
- if f == nil || f.depMode == nil {
- return "external"
- }
- return f.depMode.String()
- }
- type tagsFlag func(string) error
- func (f tagsFlag) Set(value string) error {
- return f(value)
- }
- func (f tagsFlag) String() string {
- return ""
- }
- func (_ *goLang) KnownDirectives() []string {
- return []string{
- "build_tags",
- "importmap_prefix",
- "prefix",
- }
- }
- func (_ *goLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
- gc := newGoConfig()
- switch cmd {
- case "fix", "update":
- fs.Var(
- tagsFlag(gc.setBuildTags),
- "build_tags",
- "comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.")
- fs.Var(
- &gzflag.ExplicitFlag{Value: &gc.prefix, IsSet: &gc.prefixSet},
- "go_prefix",
- "prefix of import paths in the current workspace")
- fs.Var(
- &externalFlag{&gc.depMode},
- "external",
- "external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/")
- }
- c.Exts[goName] = gc
- }
- func (_ *goLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
- // The base of the -go_prefix flag may be used to generate proto_library
- // rule names when there are no .proto sources (empty rules to be deleted)
- // or when the package name can't be determined.
- // TODO(jayconrod): deprecate and remove this behavior.
- gc := getGoConfig(c)
- pc := proto.GetProtoConfig(c)
- pc.GoPrefix = gc.prefix
- return nil
- }
- func (_ *goLang) Configure(c *config.Config, rel string, f *rule.File) {
- var gc *goConfig
- if raw, ok := c.Exts[goName]; !ok {
- gc = newGoConfig()
- } else {
- gc = raw.(*goConfig).clone()
- }
- c.Exts[goName] = gc
- if path.Base(rel) == "vendor" {
- gc.importMapPrefix = inferImportPath(gc, rel)
- gc.importMapPrefixRel = rel
- gc.prefix = ""
- gc.prefixRel = rel
- }
- if f != nil {
- setPrefix := func(prefix string) {
- if err := checkPrefix(prefix); err != nil {
- log.Print(err)
- return
- }
- gc.prefix = prefix
- gc.prefixSet = true
- gc.prefixRel = rel
- }
- for _, d := range f.Directives {
- switch d.Key {
- case "build_tags":
- if err := gc.setBuildTags(d.Value); err != nil {
- log.Print(err)
- continue
- }
- gc.preprocessTags()
- gc.setBuildTags(d.Value)
- case "importmap_prefix":
- gc.importMapPrefix = d.Value
- gc.importMapPrefixRel = rel
- case "prefix":
- setPrefix(d.Value)
- }
- }
- if !gc.prefixSet {
- for _, r := range f.Rules {
- switch r.Kind() {
- case "go_prefix":
- args := r.Args()
- if len(args) != 1 {
- continue
- }
- s, ok := args[0].(*bzl.StringExpr)
- if !ok {
- continue
- }
- setPrefix(s.Value)
- case "gazelle":
- if prefix := r.AttrString("prefix"); prefix != "" {
- setPrefix(prefix)
- }
- }
- }
- }
- }
- }
- // checkPrefix checks that a string may be used as a prefix. We forbid local
- // (relative) imports and those beginning with "/". We allow the empty string,
- // but generated rules must not have an empty importpath.
- func checkPrefix(prefix string) error {
- if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) {
- return fmt.Errorf("invalid prefix: %q", prefix)
- }
- return nil
- }
|