config.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. "os"
  19. "path"
  20. "path/filepath"
  21. "strings"
  22. "github.com/bazelbuild/bazel-gazelle/config"
  23. gzflag "github.com/bazelbuild/bazel-gazelle/flag"
  24. "github.com/bazelbuild/bazel-gazelle/language/proto"
  25. "github.com/bazelbuild/bazel-gazelle/rule"
  26. bzl "github.com/bazelbuild/buildtools/build"
  27. )
  28. // goConfig contains configuration values related to Go rules.
  29. type goConfig struct {
  30. // genericTags is a set of tags that Gazelle considers to be true. Set with
  31. // -build_tags or # gazelle:build_tags. Some tags, like gc, are always on.
  32. genericTags map[string]bool
  33. // prefix is a prefix of an import path, used to generate importpath
  34. // attributes. Set with -go_prefix or # gazelle:prefix.
  35. prefix string
  36. // prefixRel is the package name of the directory where the prefix was set
  37. // ("" for the root directory).
  38. prefixRel string
  39. // prefixSet indicates whether the prefix was set explicitly. It is an error
  40. // to infer an importpath for a rule without setting the prefix.
  41. prefixSet bool
  42. // importMapPrefix is a prefix of a package path, used to generate importmap
  43. // attributes. Set with # gazelle:importmap_prefix.
  44. importMapPrefix string
  45. // importMapPrefixRel is the package name of the directory where importMapPrefix
  46. // was set ("" for the root directory).
  47. importMapPrefixRel string
  48. // depMode determines how imports that are not standard, indexed, or local
  49. // (under the current prefix) should be resolved.
  50. depMode dependencyMode
  51. // goProtoCompilers is the protocol buffers compiler(s) to use for go code.
  52. goProtoCompilers []string
  53. // goProtoCompilersSet indicates whether goProtoCompiler was set explicitly.
  54. goProtoCompilersSet bool
  55. // goGrpcCompilers is the gRPC compiler(s) to use for go code.
  56. goGrpcCompilers []string
  57. // goGrpcCompilersSet indicates whether goGrpcCompiler was set explicitly.
  58. goGrpcCompilersSet bool
  59. // goRepositoryMode is true if Gazelle was invoked by a go_repository rule.
  60. // In this mode, we won't go out to the network to resolve external deps.
  61. goRepositoryMode bool
  62. // By default, internal packages are only visible to its siblings.
  63. // goVisibility adds a list of packages the internal packages should be
  64. // visible to
  65. goVisibility []string
  66. // moduleMode is true if the current directory is intended to be built
  67. // as part of a module. Minimal module compatibility won't be supported
  68. // if this is true in the root directory. External dependencies may be
  69. // resolved differently (also depending on goRepositoryMode).
  70. moduleMode bool
  71. // submodules is a list of modules which have the current module's path
  72. // as a prefix of their own path. This affects visibility attributes
  73. // in internal packages.
  74. submodules []moduleRepo
  75. // buildExternalAttr, buildFileNamesAttr, buildFileGenerationAttr,
  76. // buildTagsAttr, buildFileProtoModeAttr, and buildExtraArgsAttr are
  77. // attributes for go_repository rules, set on the command line.
  78. buildExternalAttr, buildFileNamesAttr, buildFileGenerationAttr, buildTagsAttr, buildFileProtoModeAttr, buildExtraArgsAttr string
  79. }
  80. var (
  81. defaultGoProtoCompilers = []string{"@io_bazel_rules_go//proto:go_proto"}
  82. defaultGoGrpcCompilers = []string{"@io_bazel_rules_go//proto:go_grpc"}
  83. )
  84. func newGoConfig() *goConfig {
  85. gc := &goConfig{
  86. goProtoCompilers: defaultGoProtoCompilers,
  87. goGrpcCompilers: defaultGoGrpcCompilers,
  88. }
  89. gc.preprocessTags()
  90. return gc
  91. }
  92. func getGoConfig(c *config.Config) *goConfig {
  93. return c.Exts[goName].(*goConfig)
  94. }
  95. func (gc *goConfig) clone() *goConfig {
  96. gcCopy := *gc
  97. gcCopy.genericTags = make(map[string]bool)
  98. for k, v := range gc.genericTags {
  99. gcCopy.genericTags[k] = v
  100. }
  101. gcCopy.goProtoCompilers = gc.goProtoCompilers[:len(gc.goProtoCompilers):len(gc.goProtoCompilers)]
  102. gcCopy.goGrpcCompilers = gc.goGrpcCompilers[:len(gc.goGrpcCompilers):len(gc.goGrpcCompilers)]
  103. gcCopy.submodules = gc.submodules[:len(gc.submodules):len(gc.submodules)]
  104. return &gcCopy
  105. }
  106. // preprocessTags adds some tags which are on by default before they are
  107. // used to match files.
  108. func (gc *goConfig) preprocessTags() {
  109. if gc.genericTags == nil {
  110. gc.genericTags = make(map[string]bool)
  111. }
  112. gc.genericTags["gc"] = true
  113. }
  114. // setBuildTags sets genericTags by parsing as a comma separated list. An
  115. // error will be returned for tags that wouldn't be recognized by "go build".
  116. // preprocessTags should be called before this.
  117. func (gc *goConfig) setBuildTags(tags string) error {
  118. if tags == "" {
  119. return nil
  120. }
  121. for _, t := range strings.Split(tags, ",") {
  122. if strings.HasPrefix(t, "!") {
  123. return fmt.Errorf("build tags can't be negated: %s", t)
  124. }
  125. gc.genericTags[t] = true
  126. }
  127. return nil
  128. }
  129. func getProtoMode(c *config.Config) proto.Mode {
  130. if pc := proto.GetProtoConfig(c); pc != nil {
  131. return pc.Mode
  132. } else {
  133. return proto.DisableGlobalMode
  134. }
  135. }
  136. // dependencyMode determines how imports of packages outside of the prefix
  137. // are resolved.
  138. type dependencyMode int
  139. const (
  140. // externalMode indicates imports should be resolved to external dependencies
  141. // (declared in WORKSPACE).
  142. externalMode dependencyMode = iota
  143. // vendorMode indicates imports should be resolved to libraries in the
  144. // vendor directory.
  145. vendorMode
  146. )
  147. func (m dependencyMode) String() string {
  148. if m == externalMode {
  149. return "external"
  150. } else {
  151. return "vendored"
  152. }
  153. }
  154. type externalFlag struct {
  155. depMode *dependencyMode
  156. }
  157. func (f *externalFlag) Set(value string) error {
  158. switch value {
  159. case "external":
  160. *f.depMode = externalMode
  161. case "vendored":
  162. *f.depMode = vendorMode
  163. default:
  164. return fmt.Errorf("unrecognized dependency mode: %q", value)
  165. }
  166. return nil
  167. }
  168. func (f *externalFlag) String() string {
  169. if f == nil || f.depMode == nil {
  170. return "external"
  171. }
  172. return f.depMode.String()
  173. }
  174. type tagsFlag func(string) error
  175. func (f tagsFlag) Set(value string) error {
  176. return f(value)
  177. }
  178. func (f tagsFlag) String() string {
  179. return ""
  180. }
  181. type moduleRepo struct {
  182. repoName, modulePath string
  183. }
  184. var validBuildExternalAttr = []string{"external", "vendored"}
  185. var validBuildFileGenerationAttr = []string{"auto", "on", "off"}
  186. var validBuildFileProtoModeAttr = []string{"default", "legacy", "disable", "disable_global", "package"}
  187. func (*goLang) KnownDirectives() []string {
  188. return []string{
  189. "build_tags",
  190. "go_grpc_compilers",
  191. "go_proto_compilers",
  192. "go_visibility",
  193. "importmap_prefix",
  194. "prefix",
  195. }
  196. }
  197. func (*goLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
  198. gc := newGoConfig()
  199. switch cmd {
  200. case "fix", "update":
  201. fs.Var(
  202. tagsFlag(gc.setBuildTags),
  203. "build_tags",
  204. "comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.")
  205. fs.Var(
  206. &gzflag.ExplicitFlag{Value: &gc.prefix, IsSet: &gc.prefixSet},
  207. "go_prefix",
  208. "prefix of import paths in the current workspace")
  209. fs.Var(
  210. &externalFlag{&gc.depMode},
  211. "external",
  212. "external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/")
  213. fs.Var(
  214. &gzflag.MultiFlag{Values: &gc.goProtoCompilers, IsSet: &gc.goProtoCompilersSet},
  215. "go_proto_compiler",
  216. "go_proto_library compiler to use (may be repeated)")
  217. fs.Var(
  218. &gzflag.MultiFlag{Values: &gc.goGrpcCompilers, IsSet: &gc.goGrpcCompilersSet},
  219. "go_grpc_compiler",
  220. "go_proto_library compiler to use for gRPC (may be repeated)")
  221. fs.BoolVar(
  222. &gc.goRepositoryMode,
  223. "go_repository_mode",
  224. false,
  225. "set when gazelle is invoked by go_repository")
  226. fs.BoolVar(
  227. &gc.moduleMode,
  228. "go_repository_module_mode",
  229. false,
  230. "set when gazelle is invoked by go_repository in module mode")
  231. case "update-repos":
  232. fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildExternalAttr, Allowed: validBuildExternalAttr},
  233. "build_external",
  234. "Sets the build_external attribute for the generated go_repository rule(s).")
  235. fs.StringVar(&gc.buildExtraArgsAttr,
  236. "build_extra_args",
  237. "",
  238. "Sets the build_extra_args attribute for the generated go_repository rule(s).")
  239. fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildFileGenerationAttr, Allowed: validBuildFileGenerationAttr},
  240. "build_file_generation",
  241. "Sets the build_file_generation attribute for the generated go_repository rule(s).")
  242. fs.StringVar(&gc.buildFileNamesAttr,
  243. "build_file_names",
  244. "",
  245. "Sets the build_file_name attribute for the generated go_repository rule(s).")
  246. fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildFileProtoModeAttr, Allowed: validBuildFileProtoModeAttr},
  247. "build_file_proto_mode",
  248. "Sets the build_file_proto_mode attribute for the generated go_repository rule(s).")
  249. fs.StringVar(&gc.buildTagsAttr,
  250. "build_tags",
  251. "",
  252. "Sets the build_tags attribute for the generated go_repository rule(s).")
  253. }
  254. c.Exts[goName] = gc
  255. }
  256. func (*goLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
  257. // The base of the -go_prefix flag may be used to generate proto_library
  258. // rule names when there are no .proto sources (empty rules to be deleted)
  259. // or when the package name can't be determined.
  260. // TODO(jayconrod): deprecate and remove this behavior.
  261. gc := getGoConfig(c)
  262. if pc := proto.GetProtoConfig(c); pc != nil {
  263. pc.GoPrefix = gc.prefix
  264. }
  265. // List modules that may refer to internal packages in this module.
  266. for _, r := range c.Repos {
  267. if r.Kind() != "go_repository" {
  268. continue
  269. }
  270. modulePath := r.AttrString("importpath")
  271. if !strings.HasPrefix(modulePath, gc.prefix+"/") {
  272. continue
  273. }
  274. m := moduleRepo{
  275. repoName: r.Name(),
  276. modulePath: modulePath,
  277. }
  278. gc.submodules = append(gc.submodules, m)
  279. }
  280. return nil
  281. }
  282. func (*goLang) Configure(c *config.Config, rel string, f *rule.File) {
  283. var gc *goConfig
  284. if raw, ok := c.Exts[goName]; !ok {
  285. gc = newGoConfig()
  286. } else {
  287. gc = raw.(*goConfig).clone()
  288. }
  289. c.Exts[goName] = gc
  290. if !gc.moduleMode {
  291. st, err := os.Stat(filepath.Join(c.RepoRoot, filepath.FromSlash(rel), "go.mod"))
  292. if err == nil && !st.IsDir() {
  293. gc.moduleMode = true
  294. }
  295. }
  296. if path.Base(rel) == "vendor" {
  297. gc.importMapPrefix = inferImportPath(gc, rel)
  298. gc.importMapPrefixRel = rel
  299. gc.prefix = ""
  300. gc.prefixRel = rel
  301. }
  302. if f != nil {
  303. setPrefix := func(prefix string) {
  304. if err := checkPrefix(prefix); err != nil {
  305. log.Print(err)
  306. return
  307. }
  308. gc.prefix = prefix
  309. gc.prefixSet = true
  310. gc.prefixRel = rel
  311. }
  312. for _, d := range f.Directives {
  313. switch d.Key {
  314. case "build_tags":
  315. if err := gc.setBuildTags(d.Value); err != nil {
  316. log.Print(err)
  317. continue
  318. }
  319. gc.preprocessTags()
  320. gc.setBuildTags(d.Value)
  321. case "go_grpc_compilers":
  322. // Special syntax (empty value) to reset directive.
  323. if d.Value == "" {
  324. gc.goGrpcCompilersSet = false
  325. gc.goGrpcCompilers = defaultGoGrpcCompilers
  326. } else {
  327. gc.goGrpcCompilersSet = true
  328. gc.goGrpcCompilers = splitValue(d.Value)
  329. }
  330. case "go_proto_compilers":
  331. // Special syntax (empty value) to reset directive.
  332. if d.Value == "" {
  333. gc.goProtoCompilersSet = false
  334. gc.goProtoCompilers = defaultGoProtoCompilers
  335. } else {
  336. gc.goProtoCompilersSet = true
  337. gc.goProtoCompilers = splitValue(d.Value)
  338. }
  339. case "go_visibility":
  340. gc.goVisibility = append(gc.goVisibility, strings.TrimSpace(d.Value))
  341. case "importmap_prefix":
  342. gc.importMapPrefix = d.Value
  343. gc.importMapPrefixRel = rel
  344. case "prefix":
  345. setPrefix(d.Value)
  346. }
  347. }
  348. if !gc.prefixSet {
  349. for _, r := range f.Rules {
  350. switch r.Kind() {
  351. case "go_prefix":
  352. args := r.Args()
  353. if len(args) != 1 {
  354. continue
  355. }
  356. s, ok := args[0].(*bzl.StringExpr)
  357. if !ok {
  358. continue
  359. }
  360. setPrefix(s.Value)
  361. case "gazelle":
  362. if prefix := r.AttrString("prefix"); prefix != "" {
  363. setPrefix(prefix)
  364. }
  365. }
  366. }
  367. }
  368. }
  369. }
  370. // checkPrefix checks that a string may be used as a prefix. We forbid local
  371. // (relative) imports and those beginning with "/". We allow the empty string,
  372. // but generated rules must not have an empty importpath.
  373. func checkPrefix(prefix string) error {
  374. if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) {
  375. return fmt.Errorf("invalid prefix: %q", prefix)
  376. }
  377. return nil
  378. }
  379. // splitDirective splits a comma-separated directive value into its component
  380. // parts, trimming each of any whitespace characters.
  381. func splitValue(value string) []string {
  382. parts := strings.Split(value, ",")
  383. values := make([]string, 0, len(parts))
  384. for _, part := range parts {
  385. values = append(values, strings.TrimSpace(part))
  386. }
  387. return values
  388. }