config.go 7.0 KB

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