generator.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "io/ioutil"
  16. "os"
  17. "path/filepath"
  18. "regexp"
  19. "sort"
  20. "strings"
  21. "github.com/bazelbuild/buildtools/build"
  22. )
  23. var (
  24. // Generator tags are specified using the format "// +k8s:name=value"
  25. genTagRe = regexp.MustCompile(`//\s*\+k8s:([^\s=]+)(?:=(\S+))\s*\n`)
  26. )
  27. // {tagName: {value: {pkgs}}} or {tagName: {pkg: {values}}}
  28. type generatorTagsMap map[string]map[string]map[string]bool
  29. // extractTags finds k8s codegen tags found in b listed in requestedTags.
  30. // It returns a map of {tag name: slice of values for that tag}.
  31. func extractTags(b []byte, requestedTags map[string]bool) map[string][]string {
  32. tags := make(map[string][]string)
  33. matches := genTagRe.FindAllSubmatch(b, -1)
  34. for _, m := range matches {
  35. if len(m) >= 3 {
  36. tag, values := string(m[1]), string(m[2])
  37. if _, requested := requestedTags[tag]; !requested {
  38. continue
  39. }
  40. tags[tag] = append(tags[tag], strings.Split(values, ",")...)
  41. }
  42. }
  43. return tags
  44. }
  45. // findGeneratorTags searches for all packages under root that include a kubernetes generator
  46. // tag comment. It does not follow symlinks, and any path in the configured skippedPaths
  47. // or codegen skipped paths is skipped.
  48. func (v *Vendorer) findGeneratorTags(root string, requestedTags map[string]bool) (tagsValuesPkgs, tagsPkgsValues generatorTagsMap, err error) {
  49. tagsValuesPkgs = make(generatorTagsMap)
  50. tagsPkgsValues = make(generatorTagsMap)
  51. err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
  52. if err != nil {
  53. return err
  54. }
  55. pkg := filepath.Dir(path)
  56. for _, r := range v.skippedK8sCodegenPaths {
  57. if r.MatchString(pkg) {
  58. return filepath.SkipDir
  59. }
  60. }
  61. if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
  62. return nil
  63. }
  64. b, err := ioutil.ReadFile(path)
  65. if err != nil {
  66. return err
  67. }
  68. for tag, values := range extractTags(b, requestedTags) {
  69. if _, present := tagsValuesPkgs[tag]; !present {
  70. tagsValuesPkgs[tag] = make(map[string]map[string]bool)
  71. }
  72. if _, present := tagsPkgsValues[tag]; !present {
  73. tagsPkgsValues[tag] = make(map[string]map[string]bool)
  74. }
  75. if _, present := tagsPkgsValues[tag][pkg]; !present {
  76. tagsPkgsValues[tag][pkg] = make(map[string]bool)
  77. }
  78. for _, v := range values {
  79. if _, present := tagsValuesPkgs[tag][v]; !present {
  80. tagsValuesPkgs[tag][v] = make(map[string]bool)
  81. }
  82. // Since multiple files in the same package may list a given tag/value, use a set to deduplicate.
  83. tagsValuesPkgs[tag][v][pkg] = true
  84. tagsPkgsValues[tag][pkg][v] = true
  85. }
  86. }
  87. return nil
  88. })
  89. if err != nil {
  90. return nil, nil, err
  91. }
  92. return
  93. }
  94. // flattened returns a copy of the map with the final stringSet flattened into a sorted slice.
  95. func flattened(m generatorTagsMap) map[string]map[string][]string {
  96. flattened := make(map[string]map[string][]string)
  97. for tag, subMap := range m {
  98. flattened[tag] = make(map[string][]string)
  99. for k, subSet := range subMap {
  100. for v := range subSet {
  101. flattened[tag][k] = append(flattened[tag][k], v)
  102. }
  103. sort.Strings(flattened[tag][k])
  104. }
  105. }
  106. return flattened
  107. }
  108. // walkGenerated generates a k8s codegen bzl file that can be parsed by Starlark
  109. // rules and macros to find packages needed k8s code generation.
  110. // This involves reading all non-test go sources in the tree and looking for
  111. // "+k8s:name=value" tags. Only those tags listed in K8sCodegenTags will be
  112. // included.
  113. // If a K8sCodegenBoilerplateFile was configured, the contents of this file
  114. // will be included as the header of the generated bzl file.
  115. // Returns true if there are diffs against the existing generated bzl file.
  116. func (v *Vendorer) walkGenerated() (bool, error) {
  117. if v.cfg.K8sCodegenBzlFile == "" {
  118. return false, nil
  119. }
  120. // only include the specified tags
  121. requestedTags := make(map[string]bool)
  122. for _, tag := range v.cfg.K8sCodegenTags {
  123. requestedTags[tag] = true
  124. }
  125. tagsValuesPkgs, tagsPkgsValues, err := v.findGeneratorTags(".", requestedTags)
  126. if err != nil {
  127. return false, err
  128. }
  129. f := &build.File{
  130. Path: v.cfg.K8sCodegenBzlFile,
  131. }
  132. addCommentBefore(f, "#################################################")
  133. addCommentBefore(f, "# # # # # # # # # # # # # # # # # # # # # # # # #")
  134. addCommentBefore(f, "This file is autogenerated by kazel. DO NOT EDIT.")
  135. addCommentBefore(f, "# # # # # # # # # # # # # # # # # # # # # # # # #")
  136. addCommentBefore(f, "#################################################")
  137. addCommentBefore(f, "")
  138. f.Stmt = append(f.Stmt, varExpr("go_prefix", "The go prefix passed to kazel", v.cfg.GoPrefix))
  139. f.Stmt = append(f.Stmt, varExpr("kazel_configured_tags", "The list of codegen tags kazel is configured to find", v.cfg.K8sCodegenTags))
  140. f.Stmt = append(f.Stmt, varExpr("tags_values_pkgs", "tags_values_pkgs is a dictionary mapping {k8s build tag: {tag value: [pkgs including that tag:value]}}", flattened(tagsValuesPkgs)))
  141. f.Stmt = append(f.Stmt, varExpr("tags_pkgs_values", "tags_pkgs_values is a dictionary mapping {k8s build tag: {pkg: [tag values in pkg]}}", flattened(tagsPkgsValues)))
  142. var boilerplate []byte
  143. if v.cfg.K8sCodegenBoilerplateFile != "" {
  144. boilerplate, err = ioutil.ReadFile(v.cfg.K8sCodegenBoilerplateFile)
  145. if err != nil {
  146. return false, err
  147. }
  148. }
  149. // Open existing file to use in diff mode.
  150. _, err = os.Stat(f.Path)
  151. if err != nil && !os.IsNotExist(err) {
  152. return false, err
  153. }
  154. return writeFile(f.Path, f, boilerplate, !os.IsNotExist(err), v.dryRun)
  155. }