| /* 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/config" |
| gzflag "github.com/bazelbuild/bazel-gazelle/flag" |
| "github.com/bazelbuild/bazel-gazelle/language/proto" |
| "github.com/bazelbuild/bazel-gazelle/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 |
| |
| // goProtoCompilers is the protocol buffers compiler(s) to use for go code. |
| goProtoCompilers []string |
| |
| // goProtoCompilersSet indicates whether goProtoCompiler was set explicitly. |
| goProtoCompilersSet bool |
| |
| // goGrpcCompilers is the gRPC compiler(s) to use for go code. |
| goGrpcCompilers []string |
| |
| // goGrpcCompilersSet indicates whether goGrpcCompiler was set explicitly. |
| goGrpcCompilersSet bool |
| } |
| |
| var ( |
| defaultGoProtoCompilers = []string{"@io_bazel_rules_go//proto:go_proto"} |
| defaultGoGrpcCompilers = []string{"@io_bazel_rules_go//proto:go_grpc"} |
| ) |
| |
| func newGoConfig() *goConfig { |
| gc := &goConfig{ |
| goProtoCompilers: defaultGoProtoCompilers, |
| goGrpcCompilers: defaultGoGrpcCompilers, |
| } |
| 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 |
| } |
| |
| func getProtoMode(c *config.Config) proto.Mode { |
| if pc := proto.GetProtoConfig(c); pc != nil { |
| return pc.Mode |
| } else { |
| return proto.DisableGlobalMode |
| } |
| } |
| |
| // 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", |
| "go_grpc_compilers", |
| "go_proto_compilers", |
| "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/") |
| fs.Var( |
| &gzflag.MultiFlag{Values: &gc.goProtoCompilers, IsSet: &gc.goProtoCompilersSet}, |
| "go_proto_compiler", |
| "go_proto_library compiler to use (may be repeated)") |
| fs.Var( |
| &gzflag.MultiFlag{Values: &gc.goGrpcCompilers, IsSet: &gc.goGrpcCompilersSet}, |
| "go_grpc_compiler", |
| "go_proto_library compiler to use for gRPC (may be repeated)") |
| } |
| 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) |
| if pc := proto.GetProtoConfig(c); pc != nil { |
| 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 "go_grpc_compilers": |
| // Special syntax (empty value) to reset directive. |
| if d.Value == "" { |
| gc.goGrpcCompilersSet = false |
| gc.goGrpcCompilers = defaultGoGrpcCompilers |
| } else { |
| gc.goGrpcCompilersSet = true |
| gc.goGrpcCompilers = splitValue(d.Value) |
| } |
| case "go_proto_compilers": |
| // Special syntax (empty value) to reset directive. |
| if d.Value == "" { |
| gc.goProtoCompilersSet = false |
| gc.goProtoCompilers = defaultGoProtoCompilers |
| } else { |
| gc.goProtoCompilersSet = true |
| gc.goProtoCompilers = splitValue(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 |
| } |
| |
| // splitDirective splits a comma-separated directive value into its component |
| // parts, trimming each of any whitespace characters. |
| func splitValue(value string) []string { |
| parts := strings.Split(value, ",") |
| values := make([]string, 0, len(parts)) |
| for _, part := range parts { |
| values = append(values, strings.TrimSpace(part)) |
| } |
| return values |
| } |