options.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. "strings"
  17. "sigs.k8s.io/controller-tools/pkg/markers"
  18. )
  19. var (
  20. InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
  21. )
  22. // +controllertools:marker:generateHelp:category=""
  23. // InputPaths represents paths and go-style path patterns to use as package roots.
  24. type InputPaths []string
  25. // RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
  26. // At this point, that's just InputPaths.
  27. func RegisterOptionsMarkers(into *markers.Registry) error {
  28. if err := into.Register(InputPathsMarker); err != nil {
  29. return err
  30. }
  31. // NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
  32. if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
  33. into.AddHelp(InputPathsMarker, helpGiver.Help())
  34. }
  35. return nil
  36. }
  37. // RegistryFromOptions produces just the marker registry that would be used by FromOptions, without
  38. // attempting to produce a full Runtime. This can be useful if you want to display help without
  39. // trying to load roots.
  40. func RegistryFromOptions(optionsRegistry *markers.Registry, options []string) (*markers.Registry, error) {
  41. protoRt, err := protoFromOptions(optionsRegistry, options)
  42. if err != nil {
  43. return nil, err
  44. }
  45. reg := &markers.Registry{}
  46. if err := protoRt.Generators.RegisterMarkers(reg); err != nil {
  47. return nil, err
  48. }
  49. return reg, nil
  50. }
  51. // FromOptions parses the options from markers stored in the given registry out into a runtime.
  52. // The markers in the registry must be either
  53. //
  54. // a) Generators
  55. // b) OutputRules
  56. // c) InputPaths
  57. //
  58. // The paths specified in InputPaths are loaded as package roots, and the combined with
  59. // the generators and the specified output rules to produce a runtime that can be run or
  60. // further modified. Not default generators are used if none are specified -- you can check
  61. // the output and rerun for that.
  62. func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, error) {
  63. protoRt, err := protoFromOptions(optionsRegistry, options)
  64. if err != nil {
  65. return nil, err
  66. }
  67. // make the runtime
  68. genRuntime, err := protoRt.Generators.ForRoots(protoRt.Paths...)
  69. if err != nil {
  70. return nil, err
  71. }
  72. // attempt to figure out what the user wants without a lot of verbose specificity:
  73. // if the user specifies a default rule, assume that they probably want to fall back
  74. // to that. Otherwise, assume that they just wanted to customize one option from the
  75. // set, and leave the rest in the standard configuration.
  76. if protoRt.OutputRules.Default != nil {
  77. genRuntime.OutputRules = protoRt.OutputRules
  78. return genRuntime, nil
  79. }
  80. outRules := DirectoryPerGenerator("config", protoRt.GeneratorsByName)
  81. for gen, rule := range protoRt.OutputRules.ByGenerator {
  82. outRules.ByGenerator[gen] = rule
  83. }
  84. genRuntime.OutputRules = outRules
  85. return genRuntime, nil
  86. }
  87. // protoFromOptions returns a proto-Runtime from the given options registry and
  88. // options set. This can then be used to construct an actual Runtime. See the
  89. // FromOptions function for more details about how the options work.
  90. func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) {
  91. var gens Generators
  92. rules := OutputRules{
  93. ByGenerator: make(map[*Generator]OutputRule),
  94. }
  95. var paths []string
  96. // collect the generators first, so that we can key the output on the actual
  97. // generator, which matters if there's settings in the gen object and it's not a pointer.
  98. outputByGen := make(map[string]OutputRule)
  99. gensByName := make(map[string]*Generator)
  100. for _, rawOpt := range options {
  101. if rawOpt[0] != '+' {
  102. rawOpt = "+" + rawOpt // add a `+` to make it acceptable for usage with the registry
  103. }
  104. defn := optionsRegistry.Lookup(rawOpt, markers.DescribesPackage)
  105. if defn == nil {
  106. return protoRuntime{}, fmt.Errorf("unknown option %q", rawOpt[1:])
  107. }
  108. val, err := defn.Parse(rawOpt)
  109. if err != nil {
  110. return protoRuntime{}, fmt.Errorf("unable to parse option %q: %w", rawOpt[1:], err)
  111. }
  112. switch val := val.(type) {
  113. case Generator:
  114. gens = append(gens, &val)
  115. gensByName[defn.Name] = &val
  116. case OutputRule:
  117. _, genName := splitOutputRuleOption(defn.Name)
  118. if genName == "" {
  119. // it's a default rule
  120. rules.Default = val
  121. continue
  122. }
  123. outputByGen[genName] = val
  124. continue
  125. case InputPaths:
  126. paths = append(paths, val...)
  127. default:
  128. return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
  129. }
  130. }
  131. // actually associate the rules now that we know the generators
  132. for genName, outputRule := range outputByGen {
  133. gen, knownGen := gensByName[genName]
  134. if !knownGen {
  135. return protoRuntime{}, fmt.Errorf("non-invoked generator %q", genName)
  136. }
  137. rules.ByGenerator[gen] = outputRule
  138. }
  139. return protoRuntime{
  140. Paths: paths,
  141. Generators: Generators(gens),
  142. OutputRules: rules,
  143. GeneratorsByName: gensByName,
  144. }, nil
  145. }
  146. // protoRuntime represents the raw pieces needed to compose a runtime, as
  147. // parsed from some options.
  148. type protoRuntime struct {
  149. Paths []string
  150. Generators Generators
  151. OutputRules OutputRules
  152. GeneratorsByName map[string]*Generator
  153. }
  154. // splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"
  155. // into its compontent rule and generator name.
  156. func splitOutputRuleOption(name string) (ruleName string, genName string) {
  157. parts := strings.SplitN(name, ":", 3)
  158. if len(parts) == 3 {
  159. // output:<generator>:<rule>
  160. return parts[2], parts[1]
  161. }
  162. // output:<rule>
  163. return parts[1], ""
  164. }