genall.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. Copyright 2019 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 genall
  14. import (
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "os"
  19. "golang.org/x/tools/go/packages"
  20. "sigs.k8s.io/yaml"
  21. "sigs.k8s.io/controller-tools/pkg/loader"
  22. "sigs.k8s.io/controller-tools/pkg/markers"
  23. )
  24. // Generators are a list of Generators.
  25. // NB(directxman12): this is a pointer so that we can uniquely identify each
  26. // instance of a generator, even if it's not hashable. Different *instances*
  27. // of a generator are treated differently.
  28. type Generators []*Generator
  29. // RegisterMarkers registers all markers defined by each of the Generators in
  30. // this list into the given registry.
  31. func (g Generators) RegisterMarkers(reg *markers.Registry) error {
  32. for _, gen := range g {
  33. if err := (*gen).RegisterMarkers(reg); err != nil {
  34. return err
  35. }
  36. }
  37. return nil
  38. }
  39. // CheckFilters returns the set of NodeFilters for all Generators that
  40. // implement NeedsTypeChecking.
  41. func (g Generators) CheckFilters() []loader.NodeFilter {
  42. var filters []loader.NodeFilter
  43. for _, gen := range g {
  44. withFilter, needsChecking := (*gen).(NeedsTypeChecking)
  45. if !needsChecking {
  46. continue
  47. }
  48. filters = append(filters, withFilter.CheckFilter())
  49. }
  50. return filters
  51. }
  52. // NeedsTypeChecking indicates that a particular generator needs & has opinions
  53. // on typechecking. If this is not implemented, a generator will be given a
  54. // context with a nil typechecker.
  55. type NeedsTypeChecking interface {
  56. // CheckFilter indicates the loader.NodeFilter (if any) that should be used
  57. // to prune out unused types/packages when type-checking (nodes for which
  58. // the filter returns true are considered "interesting"). This filter acts
  59. // as a baseline -- all types the pass through this filter will be checked,
  60. // but more than that may also be checked due to other generators' filters.
  61. CheckFilter() loader.NodeFilter
  62. }
  63. // Generator knows how to register some set of markers, and then produce
  64. // output artifacts based on loaded code containing those markers,
  65. // sharing common loaded data.
  66. type Generator interface {
  67. // RegisterMarkers registers all markers needed by this Generator
  68. // into the given registry.
  69. RegisterMarkers(into *markers.Registry) error
  70. // Generate generates artifacts produced by this marker.
  71. // It's called *after* RegisterMarkers has been called.
  72. Generate(*GenerationContext) error
  73. }
  74. // HasHelp is some Generator, OutputRule, etc with a help method.
  75. type HasHelp interface {
  76. // Help returns help for this generator.
  77. Help() *markers.DefinitionHelp
  78. }
  79. // Runtime collects generators, loaded program data (Collector, root Packages),
  80. // and I/O rules, running them together.
  81. type Runtime struct {
  82. // Generators are the Generators to be run by this Runtime.
  83. Generators Generators
  84. // GenerationContext is the base generation context that's copied
  85. // to produce the context for each Generator.
  86. GenerationContext
  87. // OutputRules defines how to output artifacts for each Generator.
  88. OutputRules OutputRules
  89. }
  90. // GenerationContext defines the common information needed for each Generator
  91. // to run.
  92. type GenerationContext struct {
  93. // Collector is the shared marker collector.
  94. Collector *markers.Collector
  95. // Roots are the base packages to be processed.
  96. Roots []*loader.Package
  97. // Checker is the shared partial type-checker.
  98. Checker *loader.TypeChecker
  99. // OutputRule describes how to output artifacts.
  100. OutputRule
  101. // InputRule describes how to load associated boilerplate artifacts.
  102. // It should *not* be used to load source files.
  103. InputRule
  104. }
  105. // WriteYAML writes the given objects out, serialized as YAML, using the
  106. // context's OutputRule. Objects are written as separate documents, separated
  107. // from each other by `---` (as per the YAML spec).
  108. func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error {
  109. out, err := g.Open(nil, itemPath)
  110. if err != nil {
  111. return err
  112. }
  113. defer out.Close()
  114. for _, obj := range objs {
  115. yamlContent, err := yaml.Marshal(obj)
  116. if err != nil {
  117. return err
  118. }
  119. n, err := out.Write(append([]byte("\n---\n"), yamlContent...))
  120. if err != nil {
  121. return err
  122. }
  123. if n < len(yamlContent) {
  124. return io.ErrShortWrite
  125. }
  126. }
  127. return nil
  128. }
  129. // ReadFile reads the given boilerplate artifact using the context's InputRule.
  130. func (g GenerationContext) ReadFile(path string) ([]byte, error) {
  131. file, err := g.OpenForRead(path)
  132. if err != nil {
  133. return nil, err
  134. }
  135. defer file.Close()
  136. return ioutil.ReadAll(file)
  137. }
  138. // ForRoots produces a Runtime to run the given generators against the
  139. // given packages. It outputs to /dev/null by default.
  140. func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) {
  141. roots, err := loader.LoadRoots(rootPaths...)
  142. if err != nil {
  143. return nil, err
  144. }
  145. rt := &Runtime{
  146. Generators: g,
  147. GenerationContext: GenerationContext{
  148. Collector: &markers.Collector{
  149. Registry: &markers.Registry{},
  150. },
  151. Roots: roots,
  152. InputRule: InputFromFileSystem,
  153. Checker: &loader.TypeChecker{
  154. NodeFilters: g.CheckFilters(),
  155. },
  156. },
  157. OutputRules: OutputRules{Default: OutputToNothing},
  158. }
  159. if err := rt.Generators.RegisterMarkers(rt.Collector.Registry); err != nil {
  160. return nil, err
  161. }
  162. return rt, nil
  163. }
  164. // Run runs the Generators in this Runtime against its packages, printing
  165. // errors (except type errors, which common result from using TypeChecker with
  166. // filters), returning true if errors were found.
  167. func (r *Runtime) Run() bool {
  168. // TODO(directxman12): we could make this parallel,
  169. // but we'd need to ensure all underlying machinery is threadsafe
  170. if len(r.Generators) == 0 {
  171. fmt.Fprintln(os.Stderr, "no generators to run")
  172. return true
  173. }
  174. hadErrs := false
  175. for _, gen := range r.Generators {
  176. ctx := r.GenerationContext // make a shallow copy
  177. ctx.OutputRule = r.OutputRules.ForGenerator(gen)
  178. // don't pass a typechecker to generators that don't provide a filter
  179. // to avoid accidents
  180. if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
  181. ctx.Checker = nil
  182. }
  183. if err := (*gen).Generate(&ctx); err != nil {
  184. fmt.Fprintln(os.Stderr, err)
  185. hadErrs = true
  186. }
  187. }
  188. // skip TypeErrors -- they're probably just from partial typechecking in crd-gen
  189. return loader.PrintErrors(r.Roots, packages.TypeError) || hadErrs
  190. }