sigs.k8s.io/controller-tools@v0.15.1-0.20240515195456-85686cb69316/pkg/genall/options.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package genall 18 19 import ( 20 "fmt" 21 "strings" 22 23 "sigs.k8s.io/controller-tools/pkg/markers" 24 ) 25 26 var ( 27 InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil))) 28 ) 29 30 // +controllertools:marker:generateHelp:category="" 31 32 // InputPaths represents paths and go-style path patterns to use as package roots. 33 // 34 // Multiple paths can be specified using "{path1, path2, path3}". 35 type InputPaths []string 36 37 // RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry. 38 // At this point, that's just InputPaths. 39 func RegisterOptionsMarkers(into *markers.Registry) error { 40 if err := into.Register(InputPathsMarker); err != nil { 41 return err 42 } 43 // NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen 44 if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp { 45 into.AddHelp(InputPathsMarker, helpGiver.Help()) 46 } 47 return nil 48 } 49 50 // RegistryFromOptions produces just the marker registry that would be used by FromOptions, without 51 // attempting to produce a full Runtime. This can be useful if you want to display help without 52 // trying to load roots. 53 func RegistryFromOptions(optionsRegistry *markers.Registry, options []string) (*markers.Registry, error) { 54 protoRt, err := protoFromOptions(optionsRegistry, options) 55 if err != nil { 56 return nil, err 57 } 58 reg := &markers.Registry{} 59 if err := protoRt.Generators.RegisterMarkers(reg); err != nil { 60 return nil, err 61 } 62 return reg, nil 63 } 64 65 // FromOptions parses the options from markers stored in the given registry out into a runtime. 66 // The markers in the registry must be either 67 // 68 // a) Generators 69 // b) OutputRules 70 // c) InputPaths 71 // 72 // The paths specified in InputPaths are loaded as package roots, and the combined with 73 // the generators and the specified output rules to produce a runtime that can be run or 74 // further modified. Not default generators are used if none are specified -- you can check 75 // the output and rerun for that. 76 func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, error) { 77 protoRt, err := protoFromOptions(optionsRegistry, options) 78 if err != nil { 79 return nil, err 80 } 81 82 // make the runtime 83 genRuntime, err := protoRt.Generators.ForRoots(protoRt.Paths...) 84 if err != nil { 85 return nil, err 86 } 87 88 // attempt to figure out what the user wants without a lot of verbose specificity: 89 // if the user specifies a default rule, assume that they probably want to fall back 90 // to that. Otherwise, assume that they just wanted to customize one option from the 91 // set, and leave the rest in the standard configuration. 92 if protoRt.OutputRules.Default != nil { 93 genRuntime.OutputRules = protoRt.OutputRules 94 return genRuntime, nil 95 } 96 97 outRules := DirectoryPerGenerator("config", protoRt.GeneratorsByName) 98 for gen, rule := range protoRt.OutputRules.ByGenerator { 99 outRules.ByGenerator[gen] = rule 100 } 101 102 genRuntime.OutputRules = outRules 103 return genRuntime, nil 104 } 105 106 // protoFromOptions returns a proto-Runtime from the given options registry and 107 // options set. This can then be used to construct an actual Runtime. See the 108 // FromOptions function for more details about how the options work. 109 func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) { 110 var gens Generators 111 rules := OutputRules{ 112 ByGenerator: make(map[*Generator]OutputRule), 113 } 114 var paths []string 115 116 // collect the generators first, so that we can key the output on the actual 117 // generator, which matters if there's settings in the gen object and it's not a pointer. 118 outputByGen := make(map[string]OutputRule) 119 gensByName := make(map[string]*Generator) 120 121 for _, rawOpt := range options { 122 if rawOpt[0] != '+' { 123 rawOpt = "+" + rawOpt // add a `+` to make it acceptable for usage with the registry 124 } 125 defn := optionsRegistry.Lookup(rawOpt, markers.DescribesPackage) 126 if defn == nil { 127 return protoRuntime{}, fmt.Errorf("unknown option %q", rawOpt[1:]) 128 } 129 130 val, err := defn.Parse(rawOpt) 131 if err != nil { 132 return protoRuntime{}, fmt.Errorf("unable to parse option %q: %w", rawOpt[1:], err) 133 } 134 135 switch val := val.(type) { 136 case Generator: 137 gens = append(gens, &val) 138 if _, alreadyExists := gensByName[defn.Name]; alreadyExists { 139 return protoRuntime{}, fmt.Errorf("multiple instances of '%s' generator specified", defn.Name) 140 } 141 gensByName[defn.Name] = &val 142 case OutputRule: 143 _, genName := splitOutputRuleOption(defn.Name) 144 if genName == "" { 145 // it's a default rule 146 rules.Default = val 147 continue 148 } 149 150 outputByGen[genName] = val 151 continue 152 case InputPaths: 153 paths = append(paths, val...) 154 default: 155 return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name) 156 } 157 } 158 159 // actually associate the rules now that we know the generators 160 for genName, outputRule := range outputByGen { 161 gen, knownGen := gensByName[genName] 162 if !knownGen { 163 return protoRuntime{}, fmt.Errorf("non-invoked generator %q", genName) 164 } 165 166 rules.ByGenerator[gen] = outputRule 167 } 168 169 return protoRuntime{ 170 Paths: paths, 171 Generators: Generators(gens), 172 OutputRules: rules, 173 GeneratorsByName: gensByName, 174 }, nil 175 } 176 177 // protoRuntime represents the raw pieces needed to compose a runtime, as 178 // parsed from some options. 179 type protoRuntime struct { 180 Paths []string 181 Generators Generators 182 OutputRules OutputRules 183 GeneratorsByName map[string]*Generator 184 } 185 186 // splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule" 187 // into its compontent rule and generator name. 188 func splitOutputRuleOption(name string) (ruleName string, genName string) { 189 parts := strings.SplitN(name, ":", 3) 190 if len(parts) == 3 { 191 // output:<generator>:<rule> 192 return parts[2], parts[1] 193 } 194 // output:<rule> 195 return parts[1], "" 196 }