193 lines
6.1 KiB
Go
193 lines
6.1 KiB
Go
|
/*
|
||
|
Copyright 2019 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package genall
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"sigs.k8s.io/controller-tools/pkg/markers"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
|
||
|
)
|
||
|
|
||
|
// +controllertools:marker:generateHelp:category=""
|
||
|
|
||
|
// InputPaths represents paths and go-style path patterns to use as package roots.
|
||
|
type InputPaths []string
|
||
|
|
||
|
// RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
|
||
|
// At this point, that's just InputPaths.
|
||
|
func RegisterOptionsMarkers(into *markers.Registry) error {
|
||
|
if err := into.Register(InputPathsMarker); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
|
||
|
if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
|
||
|
into.AddHelp(InputPathsMarker, helpGiver.Help())
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RegistryFromOptions produces just the marker registry that would be used by FromOptions, without
|
||
|
// attempting to produce a full Runtime. This can be useful if you want to display help without
|
||
|
// trying to load roots.
|
||
|
func RegistryFromOptions(optionsRegistry *markers.Registry, options []string) (*markers.Registry, error) {
|
||
|
protoRt, err := protoFromOptions(optionsRegistry, options)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
reg := &markers.Registry{}
|
||
|
if err := protoRt.Generators.RegisterMarkers(reg); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return reg, nil
|
||
|
}
|
||
|
|
||
|
// FromOptions parses the options from markers stored in the given registry out into a runtime.
|
||
|
// The markers in the registry must be either
|
||
|
//
|
||
|
// a) Generators
|
||
|
// b) OutputRules
|
||
|
// c) InputPaths
|
||
|
//
|
||
|
// The paths specified in InputPaths are loaded as package roots, and the combined with
|
||
|
// the generators and the specified output rules to produce a runtime that can be run or
|
||
|
// further modified. Not default generators are used if none are specified -- you can check
|
||
|
// the output and rerun for that.
|
||
|
func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, error) {
|
||
|
|
||
|
protoRt, err := protoFromOptions(optionsRegistry, options)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// make the runtime
|
||
|
genRuntime, err := protoRt.Generators.ForRoots(protoRt.Paths...)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// attempt to figure out what the user wants without a lot of verbose specificity:
|
||
|
// if the user specifies a default rule, assume that they probably want to fall back
|
||
|
// to that. Otherwise, assume that they just wanted to customize one option from the
|
||
|
// set, and leave the rest in the standard configuration.
|
||
|
if protoRt.OutputRules.Default != nil {
|
||
|
genRuntime.OutputRules = protoRt.OutputRules
|
||
|
return genRuntime, nil
|
||
|
}
|
||
|
|
||
|
outRules := DirectoryPerGenerator("config", protoRt.GeneratorsByName)
|
||
|
for gen, rule := range protoRt.OutputRules.ByGenerator {
|
||
|
outRules.ByGenerator[gen] = rule
|
||
|
}
|
||
|
|
||
|
genRuntime.OutputRules = outRules
|
||
|
return genRuntime, nil
|
||
|
}
|
||
|
|
||
|
// protoFromOptions returns a proto-Runtime from the given options registry and
|
||
|
// options set. This can then be used to construct an actual Runtime. See the
|
||
|
// FromOptions function for more details about how the options work.
|
||
|
func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) {
|
||
|
var gens Generators
|
||
|
rules := OutputRules{
|
||
|
ByGenerator: make(map[*Generator]OutputRule),
|
||
|
}
|
||
|
var paths []string
|
||
|
|
||
|
// collect the generators first, so that we can key the output on the actual
|
||
|
// generator, which matters if there's settings in the gen object and it's not a pointer.
|
||
|
outputByGen := make(map[string]OutputRule)
|
||
|
gensByName := make(map[string]*Generator)
|
||
|
|
||
|
for _, rawOpt := range options {
|
||
|
if rawOpt[0] != '+' {
|
||
|
rawOpt = "+" + rawOpt // add a `+` to make it acceptable for usage with the registry
|
||
|
}
|
||
|
defn := optionsRegistry.Lookup(rawOpt, markers.DescribesPackage)
|
||
|
if defn == nil {
|
||
|
return protoRuntime{}, fmt.Errorf("unknown option %q", rawOpt[1:])
|
||
|
}
|
||
|
|
||
|
val, err := defn.Parse(rawOpt)
|
||
|
if err != nil {
|
||
|
return protoRuntime{}, fmt.Errorf("unable to parse option %q: %w", rawOpt[1:], err)
|
||
|
}
|
||
|
|
||
|
switch val := val.(type) {
|
||
|
case Generator:
|
||
|
gens = append(gens, &val)
|
||
|
gensByName[defn.Name] = &val
|
||
|
case OutputRule:
|
||
|
_, genName := splitOutputRuleOption(defn.Name)
|
||
|
if genName == "" {
|
||
|
// it's a default rule
|
||
|
rules.Default = val
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
outputByGen[genName] = val
|
||
|
continue
|
||
|
case InputPaths:
|
||
|
paths = append(paths, val...)
|
||
|
default:
|
||
|
return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// actually associate the rules now that we know the generators
|
||
|
for genName, outputRule := range outputByGen {
|
||
|
gen, knownGen := gensByName[genName]
|
||
|
if !knownGen {
|
||
|
return protoRuntime{}, fmt.Errorf("non-invoked generator %q", genName)
|
||
|
}
|
||
|
|
||
|
rules.ByGenerator[gen] = outputRule
|
||
|
}
|
||
|
|
||
|
return protoRuntime{
|
||
|
Paths: paths,
|
||
|
Generators: Generators(gens),
|
||
|
OutputRules: rules,
|
||
|
GeneratorsByName: gensByName,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// protoRuntime represents the raw pieces needed to compose a runtime, as
|
||
|
// parsed from some options.
|
||
|
type protoRuntime struct {
|
||
|
Paths []string
|
||
|
Generators Generators
|
||
|
OutputRules OutputRules
|
||
|
GeneratorsByName map[string]*Generator
|
||
|
}
|
||
|
|
||
|
// splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"
|
||
|
// into its compontent rule and generator name.
|
||
|
func splitOutputRuleOption(name string) (ruleName string, genName string) {
|
||
|
parts := strings.SplitN(name, ":", 3)
|
||
|
if len(parts) == 3 {
|
||
|
// output:<generator>:<rule>
|
||
|
return parts[2], parts[1]
|
||
|
}
|
||
|
// output:<rule>
|
||
|
return parts[1], ""
|
||
|
}
|