options.go 6.3 KB

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