mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-11 01:41:54 -04:00
Use generator utilities in all API package definition generators
This commit is contained in:
parent
05a1a7f2db
commit
bd065151bb
25 changed files with 949 additions and 220 deletions
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
|
|
@ -43,6 +44,8 @@ type Args struct {
|
|||
ExternalApplyConfigurations map[types.Name]string
|
||||
|
||||
OpenAPISchemaFilePath string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -74,6 +77,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet, inputBase string) {
|
|||
"For example: k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1")
|
||||
fs.StringVar(&args.OpenAPISchemaFilePath, "openapi-schema", "",
|
||||
"path to the openapi schema containing all the types that apply configurations will be generated for")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -84,5 +88,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputPkg) == 0 {
|
||||
return fmt.Errorf("--output-pkg must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -32,6 +33,7 @@ import (
|
|||
"k8s.io/code-generator/cmd/applyconfiguration-gen/args"
|
||||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
)
|
||||
|
||||
|
|
@ -62,7 +64,12 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
pkgTypes := packageTypesForInputs(context, args.OutputPkg)
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
pkgTypes := packageTypesForInputs(context, args.OutputPkg, idOpts)
|
||||
initialTypes := args.ExternalApplyConfigurations
|
||||
refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes)
|
||||
typeModels, err := newTypeModels(args.OpenAPISchemaFilePath, pkgTypes)
|
||||
|
|
@ -252,10 +259,17 @@ func goName(gv clientgentypes.GroupVersion, p *types.Package) (string, error) {
|
|||
return goName, nil
|
||||
}
|
||||
|
||||
func packageTypesForInputs(context *generator.Context, outPkgBase string) map[string]*types.Package {
|
||||
func packageTypesForInputs(context *generator.Context, outPkgBase string, idOpts []apidefinitions.Option) map[string]*types.Package {
|
||||
pkgTypes := map[string]*types.Package{}
|
||||
for _, inputDir := range context.Inputs {
|
||||
p := context.Universe.Package(inputDir)
|
||||
info, err := apidefinitions.Identify(p, apidefinitions.ApplyConfiguration, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
internal := isInternalPackage(p)
|
||||
if internal {
|
||||
klog.Warningf("Skipping internal package: %s", p.Path)
|
||||
|
|
@ -280,13 +294,12 @@ func groupVersion(p *types.Package) (gv clientgentypes.GroupVersion, err error)
|
|||
// If there's a comment of the form "// +groupName=somegroup" or
|
||||
// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
|
||||
// group when generating.
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, p.Comments)
|
||||
|
||||
if err != nil {
|
||||
override, err := apidefinitions.GroupNameForPackage(p.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
return gv, err
|
||||
}
|
||||
if values, ok := override["groupName"]; ok {
|
||||
gv.Group = clientgentypes.Group(values[0])
|
||||
if err == nil {
|
||||
gv.Group = clientgentypes.Group(override)
|
||||
}
|
||||
|
||||
return gv, nil
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
|
|
@ -64,6 +65,8 @@ type Args struct {
|
|||
|
||||
// PrefersProtobuf determines if the generated clientset uses protobuf for API requests.
|
||||
PrefersProtobuf bool
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
func New() *Args {
|
||||
|
|
@ -104,6 +107,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet, inputBase string) {
|
|||
"optional package of apply configurations, generated by applyconfiguration-gen, that are required to generate Apply functions for each type in the clientset. By default Apply functions are not generated.")
|
||||
fs.BoolVar(&args.PrefersProtobuf, "prefers-protobuf", args.PrefersProtobuf,
|
||||
"when set, client-gen will generate a clientset that uses protobuf for API requests")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
|
||||
// support old flags
|
||||
fs.SetNormalizeFunc(mapFlagName("clientset-path", "output-pkg", fs.GetNormalizeFunc()))
|
||||
|
|
@ -122,6 +126,9 @@ func (args *Args) Validate() error {
|
|||
if len(args.ClientsetAPIPath) == 0 {
|
||||
return fmt.Errorf("--clientset-api-path cannot be empty")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -28,6 +29,7 @@ import (
|
|||
"k8s.io/code-generator/cmd/client-gen/generators/scheme"
|
||||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
codegennamer "k8s.io/code-generator/pkg/namer"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/gengo/v2"
|
||||
|
|
@ -274,20 +276,18 @@ NextGroup:
|
|||
// applyGroupOverrides applies group name overrides to each package, if applicable. If there is a
|
||||
// comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io", use the
|
||||
// first field (somegroup) as the name of the group in Go code, e.g. as the func name in a clientset.
|
||||
//
|
||||
// If the first field of the groupName is not unique within the clientset, use "// +groupName=unique
|
||||
func applyGroupOverrides(universe types.Universe, args *args.Args) error {
|
||||
// Create a map from "old GV" to "new GV" so we know what changes we need to make.
|
||||
changes := make(map[clientgentypes.GroupVersion]clientgentypes.GroupVersion)
|
||||
for gv, inputDir := range args.GroupVersionPackages() {
|
||||
p := universe.Package(inputDir)
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, p.Comments)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot extract groupName tags: %w", err)
|
||||
override, err := apidefinitions.GroupNameForPackage(p.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
return err
|
||||
}
|
||||
if override["groupName"] != nil {
|
||||
if err == nil {
|
||||
newGV := clientgentypes.GroupVersion{
|
||||
Group: clientgentypes.Group(override["groupName"][0]),
|
||||
Group: clientgentypes.Group(override),
|
||||
Version: gv.Version,
|
||||
}
|
||||
changes[gv] = newGV
|
||||
|
|
@ -363,11 +363,24 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("cannot apply group overrides: %v", err)
|
||||
}
|
||||
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
gvToTypes := map[clientgentypes.GroupVersion][]*types.Type{}
|
||||
groupGoNames := make(map[clientgentypes.GroupVersion]string)
|
||||
for gv, inputDir := range args.GroupVersionPackages() {
|
||||
p := context.Universe.Package(inputDir)
|
||||
|
||||
info, err := apidefinitions.Identify(p, apidefinitions.Client, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as
|
||||
// the Go group identifier in CamelCase. It defaults
|
||||
groupGoNames[gv] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/namer"
|
||||
"k8s.io/gengo/v2/types"
|
||||
|
||||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
)
|
||||
|
||||
// genGroup produces a file for a group client, e.g. ExtensionsClient for the extension group.
|
||||
|
|
@ -73,12 +73,12 @@ func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer
|
|||
// allow user to define a group name that's different from the one parsed from the directory.
|
||||
p := c.Universe.Package(g.inputPackage)
|
||||
groupName := g.group
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, p.Comments)
|
||||
if err != nil {
|
||||
override, err := apidefinitions.GroupNameForPackage(p.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
return err
|
||||
}
|
||||
if values, ok := override["groupName"]; ok {
|
||||
groupName = values[0]
|
||||
if err == nil {
|
||||
groupName = override
|
||||
}
|
||||
|
||||
apiPath := `"` + g.apiPath + `"`
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
)
|
||||
|
||||
|
|
@ -60,6 +61,8 @@ type Args struct {
|
|||
// groups of generators (external API that depends on Kube generations) should
|
||||
// keep tags distinct as well.
|
||||
GeneratedBuildTag string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -84,6 +87,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
fs.StringVar(&args.GeneratedBuildTag, "build-tag", args.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -91,5 +95,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputFile) == 0 {
|
||||
return fmt.Errorf("--output-file must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"k8s.io/code-generator/cmd/conversion-gen/args"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/namer"
|
||||
|
|
@ -43,9 +44,6 @@ const (
|
|||
// e.g. "+k8s:conversion-gen:explicit-from=net/url.Values" in the type comment
|
||||
// will result in generating conversion from net/url.Values.
|
||||
explicitFromTagName = "k8s:conversion-gen:explicit-from"
|
||||
// e.g., "+k8s:conversion-gen-external-types=<type-pkg>" in doc.go, where
|
||||
// <type-pkg> is the relative path to the package the types are defined in.
|
||||
externalTypesTagName = "k8s:conversion-gen-external-types"
|
||||
)
|
||||
|
||||
func extractTagValues(tagName string, comments []string) ([]string, error) {
|
||||
|
|
@ -72,10 +70,6 @@ func extractExplicitFromTag(comments []string) ([]string, error) {
|
|||
return extractTagValues(explicitFromTagName, comments)
|
||||
}
|
||||
|
||||
func extractExternalTypesTag(comments []string) ([]string, error) {
|
||||
return extractTagValues(externalTypesTagName, comments)
|
||||
}
|
||||
|
||||
func isCopyOnly(comments []string) (bool, error) {
|
||||
values, err := extractTagValues("k8s:conversion-fn", comments)
|
||||
if err != nil {
|
||||
|
|
@ -223,7 +217,12 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
targets := []generator.Target{}
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
targetList := []generator.Target{}
|
||||
|
||||
// Accumulate pre-existing conversion functions.
|
||||
// TODO: This is too ad-hoc. We need a better way.
|
||||
|
|
@ -243,54 +242,36 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
otherPkgs := make([]string, 0, len(context.Inputs))
|
||||
pkgToPeers := map[string][]string{}
|
||||
pkgToExternal := map[string]string{}
|
||||
for _, i := range context.Inputs {
|
||||
klog.V(3).Infof("pre-processing pkg %q", i)
|
||||
|
||||
for _, i := range context.Inputs {
|
||||
klog.V(3).Infof("considering pkg %q", i)
|
||||
pkg := context.Universe[i]
|
||||
|
||||
// Only generate conversions for packages which explicitly request it
|
||||
// by specifying one or more "+k8s:conversion-gen=<peer-pkg>"
|
||||
// in their doc.go file.
|
||||
peerPkgs, err := extractTag(pkg.Comments)
|
||||
if peerPkgs == nil {
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Conversion, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
klog.V(3).Infof(" no tag")
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("failed to extract tag %s", err)
|
||||
continue
|
||||
}
|
||||
klog.V(3).Infof(" tags: %q", peerPkgs)
|
||||
if len(peerPkgs) == 1 && peerPkgs[0] == "false" {
|
||||
// If a single +k8s:conversion-gen=false tag is defined, we still want
|
||||
// the generator to fire for this package for explicit conversions, but
|
||||
// we are clearing the peerPkgs to not generate any standard conversions.
|
||||
peerPkgs = nil
|
||||
} else {
|
||||
// Save peers for each input
|
||||
pkgToPeers[i] = peerPkgs
|
||||
}
|
||||
otherPkgs = append(otherPkgs, peerPkgs...)
|
||||
// Keep this one for further processing.
|
||||
filteredInputs = append(filteredInputs, i)
|
||||
|
||||
// if the external types are not in the same package where the
|
||||
// conversion functions to be generated
|
||||
externalTypesValues, err := extractExternalTypesTag(pkg.Comments)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed to extract external types tag for package %q: %v", i, err)
|
||||
// Sole +k8s:conversion-gen=false: emit only the package's
|
||||
// hand-written conversions, no peer-driven standard conversions.
|
||||
if !info.IsExplicitOnly() {
|
||||
peerPkgs := info.PeerPackages()
|
||||
klog.V(3).Infof(" peers: %q", peerPkgs)
|
||||
pkgToPeers[i] = peerPkgs
|
||||
otherPkgs = append(otherPkgs, peerPkgs...)
|
||||
}
|
||||
if externalTypesValues != nil {
|
||||
if len(externalTypesValues) != 1 {
|
||||
klog.Fatalf(" expect only one value for %q tag, got: %q", externalTypesTagName, externalTypesValues)
|
||||
}
|
||||
externalTypes := externalTypesValues[0]
|
||||
klog.V(3).Infof(" external types tags: %q", externalTypes)
|
||||
|
||||
externalTypes := info.ExternalTypes()
|
||||
if externalTypes != i {
|
||||
klog.V(3).Infof(" external types: %q", externalTypes)
|
||||
otherPkgs = append(otherPkgs, externalTypes)
|
||||
pkgToExternal[i] = externalTypes
|
||||
} else {
|
||||
pkgToExternal[i] = i
|
||||
}
|
||||
pkgToExternal[i] = externalTypes
|
||||
}
|
||||
|
||||
// Make sure explicit peer-packages are added.
|
||||
|
|
@ -346,7 +327,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
unsafeEquality = noEquality{}
|
||||
}
|
||||
|
||||
targets = append(targets,
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: path.Base(pkg.Path),
|
||||
PkgPath: pkg.Path,
|
||||
|
|
@ -377,7 +358,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
memoryEquivalentTypes.Skip(k.inType, k.outType)
|
||||
}
|
||||
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
type equalMemoryTypes map[conversionPair]bool
|
||||
|
|
|
|||
|
|
@ -20,11 +20,15 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
OutputFile string
|
||||
GoHeaderFile string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -38,6 +42,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"the name of the file to be generated")
|
||||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -45,5 +50,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputFile) == 0 {
|
||||
return fmt.Errorf("--output-file must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"k8s.io/code-generator/cmd/deepcopy-gen/args"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
|
|
@ -128,13 +129,28 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
targets := []generator.Target{}
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
targetList := []generator.Target{}
|
||||
|
||||
for _, i := range context.Inputs {
|
||||
klog.V(3).Infof("Considering pkg %q", i)
|
||||
|
||||
klog.V(3).Infof("considering pkg %q", i)
|
||||
pkg := context.Universe[i]
|
||||
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Deepcopy, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
klog.V(3).Infof(" inactive (no +k8s:deepcopy-gen, no type-level opt-in, or =false)")
|
||||
continue
|
||||
}
|
||||
|
||||
// extractEnabledTag also parses the comma-separated subparams
|
||||
// (e.g. ",register=true"), which Target.Values does not.
|
||||
ptag := extractEnabledTag(pkg.Comments)
|
||||
ptagValue := ""
|
||||
ptagRegister := false
|
||||
|
|
@ -189,7 +205,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
|
||||
if pkgNeedsGeneration {
|
||||
klog.V(3).Infof("Package %q needs generation", i)
|
||||
targets = append(targets,
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: strings.Split(path.Base(pkg.Path), ".")[0],
|
||||
PkgPath: pkg.Path,
|
||||
|
|
@ -206,7 +222,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
})
|
||||
}
|
||||
}
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
// genDeepCopy produces a file with autogenerated deep-copy functions.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
Copyright 2025 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 generators
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"k8s.io/code-generator/cmd/deepcopy-gen/args"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
// copyableStruct returns a struct type with a single primitive int32 field.
|
||||
// copyableType in deepcopy.go accepts a non-private struct kind.
|
||||
func copyableStruct(pkgPath, name string, comments []string) *types.Type {
|
||||
return &types.Type{
|
||||
Name: types.Name{Package: pkgPath, Name: name},
|
||||
Kind: types.Struct,
|
||||
CommentLines: comments,
|
||||
Members: []types.Member{
|
||||
{Name: "X", Type: types.Int32},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTargets(t *testing.T) {
|
||||
type pkgSpec struct {
|
||||
path string
|
||||
comments []string
|
||||
// types maps type-name to its CommentLines (a copyable struct is
|
||||
// synthesized for each entry). nil means no types in the package.
|
||||
types map[string][]string
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
pkgs []pkgSpec
|
||||
wantPkgs []string
|
||||
// wantAllTypes maps PkgPath -> expected genDeepCopy.allTypes value.
|
||||
wantAllTypes map[string]bool
|
||||
// wantRegister maps PkgPath -> expected genDeepCopy.registerTypes value.
|
||||
wantRegister map[string]bool
|
||||
}{
|
||||
{
|
||||
name: "package tag with copyable struct activates",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/a",
|
||||
comments: []string{"+k8s:deepcopy-gen=package"},
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: []string{"example.com/pkg/a"},
|
||||
wantAllTypes: map[string]bool{"example.com/pkg/a": true},
|
||||
wantRegister: map[string]bool{"example.com/pkg/a": false},
|
||||
},
|
||||
{
|
||||
name: "package tag with register=false activates with register=false",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/b",
|
||||
comments: []string{"+k8s:deepcopy-gen=package,register=false"},
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: []string{"example.com/pkg/b"},
|
||||
wantAllTypes: map[string]bool{"example.com/pkg/b": true},
|
||||
wantRegister: map[string]bool{"example.com/pkg/b": false},
|
||||
},
|
||||
{
|
||||
name: "package tag with register=true activates with register=true",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/c",
|
||||
comments: []string{"+k8s:deepcopy-gen=package,register=true"},
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: []string{"example.com/pkg/c"},
|
||||
wantAllTypes: map[string]bool{"example.com/pkg/c": true},
|
||||
wantRegister: map[string]bool{"example.com/pkg/c": true},
|
||||
},
|
||||
{
|
||||
name: "package opted out is skipped",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/d",
|
||||
comments: []string{"+k8s:deepcopy-gen=false"},
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: nil,
|
||||
},
|
||||
{
|
||||
name: "no package tag but type opts in activates",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/e",
|
||||
types: map[string][]string{
|
||||
"T": {"+k8s:deepcopy-gen=true"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPkgs: []string{"example.com/pkg/e"},
|
||||
wantAllTypes: map[string]bool{"example.com/pkg/e": false},
|
||||
wantRegister: map[string]bool{"example.com/pkg/e": false},
|
||||
},
|
||||
{
|
||||
name: "package tag but no copyable types is skipped",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/f",
|
||||
comments: []string{"+k8s:deepcopy-gen=package"},
|
||||
types: nil,
|
||||
},
|
||||
},
|
||||
wantPkgs: nil,
|
||||
},
|
||||
{
|
||||
name: "no tag and no opt-in types is skipped",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/g",
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: nil,
|
||||
},
|
||||
// Ecosystem regression: a third-party generator's tag in the same
|
||||
// doc.go must NOT cause deepcopy-gen to fail. The deepcopy-gen
|
||||
// tag still activates as expected.
|
||||
{
|
||||
name: "foreign third-party generator tag is ignored",
|
||||
pkgs: []pkgSpec{
|
||||
{
|
||||
path: "example.com/pkg/h",
|
||||
comments: []string{
|
||||
"+k8s:my-custom-gen=value",
|
||||
"+k8s:deepcopy-gen=package",
|
||||
},
|
||||
types: map[string][]string{"T": nil},
|
||||
},
|
||||
},
|
||||
wantPkgs: []string{"example.com/pkg/h"},
|
||||
wantAllTypes: map[string]bool{"example.com/pkg/h": true},
|
||||
wantRegister: map[string]bool{"example.com/pkg/h": false},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
universe := types.Universe{}
|
||||
var inputs []string
|
||||
for _, ps := range tc.pkgs {
|
||||
pkg := &types.Package{
|
||||
Path: ps.path,
|
||||
Dir: ps.path,
|
||||
Name: "pkg",
|
||||
Comments: ps.comments,
|
||||
Types: map[string]*types.Type{},
|
||||
}
|
||||
for tname, tcomments := range ps.types {
|
||||
pkg.Types[tname] = copyableStruct(ps.path, tname, tcomments)
|
||||
}
|
||||
universe[ps.path] = pkg
|
||||
inputs = append(inputs, ps.path)
|
||||
}
|
||||
|
||||
ctx := &generator.Context{
|
||||
Universe: universe,
|
||||
Inputs: inputs,
|
||||
}
|
||||
|
||||
result := GetTargets(ctx, args.New())
|
||||
|
||||
var gotPkgs []string
|
||||
for _, tgt := range result {
|
||||
gotPkgs = append(gotPkgs, tgt.Path())
|
||||
}
|
||||
sort.Strings(gotPkgs)
|
||||
want := append([]string(nil), tc.wantPkgs...)
|
||||
sort.Strings(want)
|
||||
if !reflect.DeepEqual(gotPkgs, want) {
|
||||
t.Errorf("PkgPaths = %v, want %v", gotPkgs, want)
|
||||
}
|
||||
|
||||
for _, tgt := range result {
|
||||
gens := tgt.Generators(ctx)
|
||||
if len(gens) != 1 {
|
||||
t.Errorf("pkg %q: got %d generators, want 1", tgt.Path(), len(gens))
|
||||
continue
|
||||
}
|
||||
gdc, ok := gens[0].(*genDeepCopy)
|
||||
if !ok {
|
||||
t.Errorf("pkg %q: generator type = %T, want *genDeepCopy", tgt.Path(), gens[0])
|
||||
continue
|
||||
}
|
||||
if want, ok := tc.wantAllTypes[tgt.Path()]; ok && gdc.allTypes != want {
|
||||
t.Errorf("pkg %q: allTypes = %v, want %v", tgt.Path(), gdc.allTypes, want)
|
||||
}
|
||||
if want, ok := tc.wantRegister[tgt.Path()]; ok && gdc.registerTypes != want {
|
||||
t.Errorf("pkg %q: registerTypes = %v, want %v", tgt.Path(), gdc.registerTypes, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
)
|
||||
|
||||
|
|
@ -34,6 +35,8 @@ type Args struct {
|
|||
// groups of generators (external API that depends on Kube generations) should
|
||||
// keep tags distinct as well.
|
||||
GeneratedBuildTag string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -52,6 +55,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
fs.StringVar(&args.GeneratedBuildTag, "build-tag", args.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -59,6 +63,9 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputFile) == 0 {
|
||||
return fmt.Errorf("--output-file must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"k8s.io/code-generator/cmd/defaulter-gen/args"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
|
|
@ -62,7 +63,6 @@ var typeZeroValue = map[string]interface{}{
|
|||
|
||||
// These are the comment tags that carry parameters for defaulter generation.
|
||||
const tagName = "k8s:defaulter-gen"
|
||||
const inputTagName = "k8s:defaulter-gen-input"
|
||||
const defaultTagName = "default"
|
||||
|
||||
func extractDefaultTag(comments []string) ([]string, error) {
|
||||
|
|
@ -87,12 +87,17 @@ func extractTag(comments []string) ([]string, bool) {
|
|||
return values, true
|
||||
}
|
||||
|
||||
func extractInputTag(comments []string) ([]string, error) {
|
||||
tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{inputTagName}, comments)
|
||||
// defaulterMatchType returns the values to be defaulted for pkg, or false
|
||||
// if defaulter-gen should not run.
|
||||
func defaulterMatchType(pkg *types.Package, idOpts []apidefinitions.Option) ([]string, bool) {
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Defaulter, idOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
klog.Fatal(err)
|
||||
}
|
||||
return tags[inputTagName], nil
|
||||
if !info.ShouldGenerate() {
|
||||
return nil, false
|
||||
}
|
||||
return info.TypeFilters(), true
|
||||
}
|
||||
|
||||
func checkTag(comments []string, require ...string) (bool, error) {
|
||||
|
|
@ -250,7 +255,12 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
targets := []generator.Target{}
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
targetList := []generator.Target{}
|
||||
|
||||
// Accumulate pre-existing default functions.
|
||||
// TODO: This is too ad-hoc. We need a better way.
|
||||
|
|
@ -265,26 +275,20 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
pkgToInput := map[string]string{}
|
||||
for _, i := range context.Inputs {
|
||||
klog.V(5).Infof("considering pkg %q", i)
|
||||
|
||||
pkg := context.Universe[i]
|
||||
|
||||
// if the types are not in the same package where the defaulter functions to be generated
|
||||
inputTags, err := extractInputTag(pkg.Comments)
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Defaulter, idOpts...)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error extracting input tag: %v", err))
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if len(inputTags) > 1 {
|
||||
panic(fmt.Sprintf("there may only be one input tag, got %#v", inputTags))
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
if len(inputTags) == 1 {
|
||||
inputPath := inputTags[0]
|
||||
if strings.HasPrefix(inputPath, "./") || strings.HasPrefix(inputPath, "../") {
|
||||
// this is a relative dir, which will not work under gomodules.
|
||||
// join with the local package path, but warn
|
||||
klog.Warningf("relative path %s=%s will not work under gomodule mode; use full package path (as used by 'import') instead", inputTagName, inputPath)
|
||||
inputPath = path.Join(pkg.Path, inputTags[0])
|
||||
}
|
||||
|
||||
// +k8s:defaulter-gen-input may direct the generator at types in
|
||||
// a different package than the one where defaulters will be emitted.
|
||||
inputPath := info.ExternalTypes()
|
||||
if inputPath != pkg.Path {
|
||||
klog.V(5).Infof(" input pkg %v", inputPath)
|
||||
inputPkgs = append(inputPkgs, inputPath)
|
||||
pkgToInput[i] = inputPath
|
||||
|
|
@ -336,7 +340,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
getManualDefaultingFunctions(context, context.Universe[pp], existingDefaulters)
|
||||
}
|
||||
|
||||
typesWith, found := extractTag(pkg.Comments)
|
||||
typesWith, found := defaulterMatchType(pkg, idOpts)
|
||||
if !found {
|
||||
klog.V(2).InfoS(" did not find required tag", "tag", tagName)
|
||||
continue
|
||||
|
|
@ -443,9 +447,12 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
|
||||
if len(newDefaulters) == 0 {
|
||||
klog.V(5).Infof("no defaulters in package %s", pkg.Name)
|
||||
if _, hasTag := extractTag(pkg.Comments); !hasTag {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
targets = append(targets,
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: path.Base(pkg.Path),
|
||||
PkgPath: pkg.Path,
|
||||
|
|
@ -463,7 +470,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
},
|
||||
})
|
||||
}
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
// callTreeForType contains fields necessary to build a tree for types.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
// Args is used by the gengo framework to pass args specific to this generator.
|
||||
|
|
@ -35,6 +37,8 @@ type Args struct {
|
|||
// PluralExceptions define a list of pluralizer exceptions in Type:PluralType format.
|
||||
// The default list is "Endpoints:Endpoints"
|
||||
PluralExceptions []string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -62,6 +66,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"if true, omit the intermediate \"internalversion\" and \"externalversions\" subdirectories")
|
||||
fs.StringSliceVar(&args.PluralExceptions, "plural-exceptions", args.PluralExceptions,
|
||||
"list of comma separated plural exception definitions in Type:PluralizedType format")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -78,5 +83,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.ListersPackage) == 0 {
|
||||
return fmt.Errorf("--listers-package must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -25,6 +26,7 @@ import (
|
|||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/cmd/informer-gen/args"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
|
|
@ -104,6 +106,11 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
externalVersionOutputPkg = path.Join(externalVersionOutputPkg, "externalversions")
|
||||
}
|
||||
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
var targetList []generator.Target
|
||||
typesForGroupVersion := make(map[clientgentypes.GroupVersion][]*types.Type)
|
||||
|
||||
|
|
@ -113,6 +120,14 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
for _, inputPkg := range context.Inputs {
|
||||
p := context.Universe.Package(inputPkg)
|
||||
|
||||
info, err := apidefinitions.Identify(p, apidefinitions.Informer, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
|
||||
objectMeta, internal, err := objectMetaForPackage(p)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
|
|
@ -144,23 +159,23 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
// If there's a comment of the form "// +groupName=somegroup" or
|
||||
// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
|
||||
// group when generating.
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, p.Comments)
|
||||
if err != nil {
|
||||
klog.Fatalf("error extracting groupName tags: %v", err)
|
||||
override, err := apidefinitions.GroupNameForPackage(p.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
klog.Fatalf("error resolving group name: %v", err)
|
||||
}
|
||||
if override["groupName"] != nil {
|
||||
gv.Group = clientgentypes.Group(override["groupName"][0])
|
||||
if err == nil {
|
||||
gv.Group = clientgentypes.Group(override)
|
||||
}
|
||||
|
||||
// If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as
|
||||
// the Go group identifier in CamelCase. It defaults
|
||||
groupGoNames[groupPackageName] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0])
|
||||
override, err = genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupGoName"}, p.Comments)
|
||||
goName, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupGoName"}, p.Comments)
|
||||
if err != nil {
|
||||
klog.Fatalf("error extracting groupGoName tags: %v", err)
|
||||
}
|
||||
if override["groupGoName"] != nil {
|
||||
groupGoNames[groupPackageName] = namer.IC(override["groupGoName"][0])
|
||||
if goName["groupGoName"] != nil {
|
||||
groupGoNames[groupPackageName] = namer.IC(goName["groupGoName"][0])
|
||||
}
|
||||
|
||||
var typesToGenerate []*types.Type
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
// Args is used by the gengo framework to pass args specific to this generator.
|
||||
|
|
@ -31,6 +33,8 @@ type Args struct {
|
|||
// PluralExceptions specify list of exceptions used when pluralizing certain types.
|
||||
// For example 'Endpoints:Endpoints', otherwise the pluralizer will generate 'Endpointes'.
|
||||
PluralExceptions []string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -48,6 +52,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"list of comma separated plural exception definitions in Type:PluralizedType format")
|
||||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -58,5 +63,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputPkg) == 0 {
|
||||
return fmt.Errorf("--output-pkg must be specified")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
|
@ -26,7 +27,7 @@ import (
|
|||
"k8s.io/code-generator/cmd/client-gen/generators/util"
|
||||
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/cmd/lister-gen/args"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/namer"
|
||||
|
|
@ -67,10 +68,23 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
var targetList []generator.Target
|
||||
for _, inputPkg := range context.Inputs {
|
||||
p := context.Universe.Package(inputPkg)
|
||||
|
||||
info, err := apidefinitions.Identify(p, apidefinitions.Lister, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
|
||||
objectMeta, internal, err := objectMetaForPackage(p)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
|
|
@ -102,12 +116,12 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
// If there's a comment of the form "// +groupName=somegroup" or
|
||||
// "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the
|
||||
// group when generating.
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, p.Comments)
|
||||
if err != nil {
|
||||
klog.Fatalf("error extracting groupName tags: %v", err)
|
||||
override, err := apidefinitions.GroupNameForPackage(p.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
klog.Fatalf("error resolving group name: %v", err)
|
||||
}
|
||||
if override["groupName"] != nil {
|
||||
gv.Group = clientgentypes.Group(strings.SplitN(override["groupName"][0], ".", 2)[0])
|
||||
if err == nil {
|
||||
gv.Group = clientgentypes.Group(strings.SplitN(override, ".", 2)[0])
|
||||
}
|
||||
|
||||
var typesToGenerate []*types.Type
|
||||
|
|
|
|||
|
|
@ -20,11 +20,15 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
OutputFile string
|
||||
GoHeaderFile string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -38,6 +42,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"the name of the file to be generated")
|
||||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -45,6 +50,8 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputFile) == 0 {
|
||||
return fmt.Errorf("--output-file must be specified")
|
||||
}
|
||||
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
|
|
@ -192,64 +193,44 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
targets := []generator.Target{}
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
targetList := []generator.Target{}
|
||||
|
||||
for _, i := range context.Inputs {
|
||||
klog.V(5).Infof("Considering pkg %q", i)
|
||||
|
||||
klog.V(5).Infof("considering pkg %q", i)
|
||||
pkg := context.Universe[i]
|
||||
|
||||
ptag := extractTag(tagEnabledName, pkg.Comments)
|
||||
pkgNeedsGeneration := false
|
||||
if ptag != nil {
|
||||
pkgNeedsGeneration, err = strconv.ParseBool(ptag.value)
|
||||
if err != nil {
|
||||
klog.Fatalf("Package %v: unsupported %s value: %q :%v", i, tagEnabledName, ptag.value, err)
|
||||
}
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.PrereleaseLifecycle, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !pkgNeedsGeneration {
|
||||
klog.V(5).Infof(" skipping package")
|
||||
if !info.ShouldGenerate() {
|
||||
klog.V(5).Infof(" not enabled")
|
||||
continue
|
||||
}
|
||||
klog.V(3).Infof("Generating package %q", pkg.Path)
|
||||
klog.V(3).Infof("generating package %q", pkg.Path)
|
||||
|
||||
// If the pkg-scoped tag says to generate, we can skip scanning types.
|
||||
if !pkgNeedsGeneration {
|
||||
// If the pkg-scoped tag did not exist, scan all types for one that
|
||||
// explicitly wants generation.
|
||||
for _, t := range pkg.Types {
|
||||
klog.V(5).Infof(" considering type %q", t.Name.String())
|
||||
ttag := extractEnabledTypeTag(t)
|
||||
if ttag != nil && ttag.value == "true" {
|
||||
klog.V(5).Infof(" tag=true")
|
||||
if !isAPIType(t) {
|
||||
klog.Fatalf("Type %v requests prerelease generation but is not an API type", t)
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: strings.Split(path.Base(pkg.Path), ".")[0],
|
||||
PkgPath: pkg.Path,
|
||||
PkgDir: pkg.Dir, // output pkg is the same as the input
|
||||
HeaderComment: boilerplate,
|
||||
FilterFunc: func(c *generator.Context, t *types.Type) bool {
|
||||
return t.Name.Package == pkg.Path
|
||||
},
|
||||
GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) {
|
||||
return []generator.Generator{
|
||||
NewPrereleaseLifecycleGen(args.OutputFile, pkg.Path),
|
||||
}
|
||||
pkgNeedsGeneration = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pkgNeedsGeneration {
|
||||
targets = append(targets,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: strings.Split(path.Base(pkg.Path), ".")[0],
|
||||
PkgPath: pkg.Path,
|
||||
PkgDir: pkg.Dir, // output pkg is the same as the input
|
||||
HeaderComment: boilerplate,
|
||||
FilterFunc: func(c *generator.Context, t *types.Type) bool {
|
||||
return t.Name.Package == pkg.Path
|
||||
},
|
||||
GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) {
|
||||
return []generator.Generator{
|
||||
NewPrereleaseLifecycleGen(args.OutputFile, pkg.Path),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
// genDeepCopy produces a file with autogenerated deep-copy functions.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
Copyright 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 prereleaselifecyclegenerators
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
func TestGetTargets(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
pkgs map[string]*types.Package
|
||||
inputs []string
|
||||
wantPkgs []string
|
||||
}{
|
||||
{
|
||||
name: "enabled package activates",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/api/v1": {
|
||||
Path: "example.com/api/v1",
|
||||
Dir: "/tmp/example/api/v1",
|
||||
Comments: []string{"+k8s:prerelease-lifecycle-gen=true"},
|
||||
},
|
||||
},
|
||||
inputs: []string{"example.com/api/v1"},
|
||||
wantPkgs: []string{"example.com/api/v1"},
|
||||
},
|
||||
{
|
||||
name: "sole =false opts out",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/api/v1": {
|
||||
Path: "example.com/api/v1",
|
||||
Dir: "/tmp/example/api/v1",
|
||||
Comments: []string{"+k8s:prerelease-lifecycle-gen=false"},
|
||||
},
|
||||
},
|
||||
inputs: []string{"example.com/api/v1"},
|
||||
wantPkgs: nil,
|
||||
},
|
||||
{
|
||||
name: "no relevant tag is skipped",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/api/v1": {
|
||||
Path: "example.com/api/v1",
|
||||
Dir: "/tmp/example/api/v1",
|
||||
Comments: []string{"+groupName=example.com"},
|
||||
},
|
||||
},
|
||||
inputs: []string{"example.com/api/v1"},
|
||||
wantPkgs: nil,
|
||||
},
|
||||
{
|
||||
name: "enabled with introduced subtag activates",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/api/v1beta1": {
|
||||
Path: "example.com/api/v1beta1",
|
||||
Dir: "/tmp/example/api/v1beta1",
|
||||
Comments: []string{
|
||||
"+k8s:prerelease-lifecycle-gen=true",
|
||||
"+k8s:prerelease-lifecycle-gen:introduced=1.30",
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: []string{"example.com/api/v1beta1"},
|
||||
wantPkgs: []string{"example.com/api/v1beta1"},
|
||||
},
|
||||
// Ecosystem regression: a third-party generator's tag in the same
|
||||
// doc.go must NOT cause prerelease-gen to fail or skip.
|
||||
{
|
||||
name: "foreign third-party generator tag is ignored",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/api/v1": {
|
||||
Path: "example.com/api/v1",
|
||||
Dir: "/tmp/example/api/v1",
|
||||
Comments: []string{
|
||||
"+k8s:my-custom-gen=value",
|
||||
"+k8s:prerelease-lifecycle-gen=true",
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: []string{"example.com/api/v1"},
|
||||
wantPkgs: []string{"example.com/api/v1"},
|
||||
},
|
||||
{
|
||||
name: "mix of all cases",
|
||||
pkgs: map[string]*types.Package{
|
||||
"example.com/enabled/v1": {
|
||||
Path: "example.com/enabled/v1",
|
||||
Dir: "/tmp/enabled/v1",
|
||||
Comments: []string{"+k8s:prerelease-lifecycle-gen=true"},
|
||||
},
|
||||
"example.com/optedout/v1": {
|
||||
Path: "example.com/optedout/v1",
|
||||
Dir: "/tmp/optedout/v1",
|
||||
Comments: []string{"+k8s:prerelease-lifecycle-gen=false"},
|
||||
},
|
||||
"example.com/untagged/v1": {
|
||||
Path: "example.com/untagged/v1",
|
||||
Dir: "/tmp/untagged/v1",
|
||||
Comments: []string{"+groupName=example.com"},
|
||||
},
|
||||
"example.com/withsubtag/v1beta1": {
|
||||
Path: "example.com/withsubtag/v1beta1",
|
||||
Dir: "/tmp/withsubtag/v1beta1",
|
||||
Comments: []string{
|
||||
"+k8s:prerelease-lifecycle-gen=true",
|
||||
"+k8s:prerelease-lifecycle-gen:introduced=1.30",
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: []string{
|
||||
"example.com/enabled/v1",
|
||||
"example.com/optedout/v1",
|
||||
"example.com/untagged/v1",
|
||||
"example.com/withsubtag/v1beta1",
|
||||
},
|
||||
wantPkgs: []string{
|
||||
"example.com/enabled/v1",
|
||||
"example.com/withsubtag/v1beta1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := &generator.Context{
|
||||
Universe: types.Universe(tc.pkgs),
|
||||
Inputs: tc.inputs,
|
||||
}
|
||||
a := &args.Args{OutputFile: "zz_generated.prerelease_lifecycle.go"}
|
||||
|
||||
got := GetTargets(ctx, a)
|
||||
|
||||
var gotPkgs []string
|
||||
for _, tgt := range got {
|
||||
gotPkgs = append(gotPkgs, tgt.Path())
|
||||
}
|
||||
sort.Strings(gotPkgs)
|
||||
want := append([]string(nil), tc.wantPkgs...)
|
||||
sort.Strings(want)
|
||||
|
||||
if diff := cmp.Diff(want, gotPkgs); diff != "" {
|
||||
t.Errorf("GetTargets package paths mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -20,11 +20,14 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
OutputFile string
|
||||
GoHeaderFile string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// New returns default arguments for the generator.
|
||||
|
|
@ -38,6 +41,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"the name of the file to be generated")
|
||||
fs.StringVar(&args.GoHeaderFile, "go-header-file", "",
|
||||
"the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -45,6 +49,9 @@ func (args *Args) Validate() error {
|
|||
if len(args.OutputFile) == 0 {
|
||||
return fmt.Errorf("output file base name cannot be empty")
|
||||
}
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
|
@ -26,7 +27,7 @@ import (
|
|||
|
||||
clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
|
||||
"k8s.io/code-generator/cmd/register-gen/args"
|
||||
genutil "k8s.io/code-generator/pkg/util"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/namer"
|
||||
|
|
@ -51,9 +52,17 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
targets := []generator.Target{}
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
targetList := []generator.Target{}
|
||||
for _, input := range context.Inputs {
|
||||
pkg := context.Universe.Package(input)
|
||||
if !isRegisterGenTarget(pkg, idOpts) {
|
||||
continue
|
||||
}
|
||||
internal, err := isInternal(pkg)
|
||||
if err != nil {
|
||||
klog.V(5).Infof("skipping the generation of %s file, due to err %v", args.OutputFile, err)
|
||||
|
|
@ -84,15 +93,13 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
|
||||
// if there is a comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io",
|
||||
// extract the fully qualified API group name from it and overwrite the group inferred from the package path
|
||||
override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupName"}, pkg.Comments)
|
||||
if err != nil {
|
||||
klog.Errorf("error extracting groupName tags: %v", err)
|
||||
continue
|
||||
override, err := apidefinitions.GroupNameForPackage(pkg.Comments)
|
||||
if err != nil && !errors.Is(err, apidefinitions.ErrGroupUndeclared) {
|
||||
klog.Fatalf("error resolving group name: %v", err)
|
||||
}
|
||||
if override["groupName"] != nil {
|
||||
groupName := override["groupName"][0]
|
||||
klog.V(5).Infof("overriding the group name with = %s", groupName)
|
||||
gv.Group = clientgentypes.Group(groupName)
|
||||
if err == nil {
|
||||
klog.V(5).Infof("overriding the group name with = %s", override)
|
||||
gv.Group = clientgentypes.Group(override)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +113,7 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
}
|
||||
}
|
||||
|
||||
targets = append(targets,
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: pkg.Name,
|
||||
PkgPath: pkg.Path, // output to same pkg as input
|
||||
|
|
@ -128,7 +135,18 @@ func GetTargets(context *generator.Context, args *args.Args) []generator.Target
|
|||
})
|
||||
}
|
||||
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
// isRegisterGenTarget reports whether pkg has opted in to register-gen.
|
||||
// Activation rules are encoded in apidefinitions.Register's Spec
|
||||
// (Boolean ActivationTag with a +groupName= fallback).
|
||||
func isRegisterGenTarget(pkg *types.Package, idOpts []apidefinitions.Option) bool {
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Register, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
return info.ShouldGenerate()
|
||||
}
|
||||
|
||||
// isInternal determines whether the given package
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
Copyright 2025 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 generators
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"k8s.io/code-generator/cmd/register-gen/args"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
func TestGetTargets(t *testing.T) {
|
||||
type pkgSpec struct {
|
||||
path string
|
||||
dir string // optional; will use t.TempDir() if needRegisterFile
|
||||
comments []string
|
||||
// typeMembers describes a single struct type in the package. nil means no types.
|
||||
typeMembers []types.Member
|
||||
// needRegisterFile, when true, creates dir/register.go before invoking GetTargets.
|
||||
needRegisterFile bool
|
||||
}
|
||||
|
||||
externalTypeMeta := []types.Member{
|
||||
{Name: "TypeMeta", Embedded: true, Tags: `json:",inline"`},
|
||||
}
|
||||
internalTypeMeta := []types.Member{
|
||||
{Name: "TypeMeta", Embedded: true},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
pkgs []pkgSpec
|
||||
wantPaths []string // package paths expected in returned targets, sorted
|
||||
wantGroups map[string]string // pkgPath -> expected group
|
||||
wantHasType map[string]bool // pkgPath -> whether typesToGenerate is non-empty
|
||||
}{
|
||||
{
|
||||
name: "external pkg with +groupName activates",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+groupName=foo.k8s.io"},
|
||||
typeMembers: externalTypeMeta,
|
||||
}},
|
||||
wantPaths: []string{"k8s.io/api/foo/v1"},
|
||||
wantGroups: map[string]string{"k8s.io/api/foo/v1": "foo.k8s.io"},
|
||||
wantHasType: map[string]bool{"k8s.io/api/foo/v1": true},
|
||||
},
|
||||
{
|
||||
name: "external pkg with +k8s:register-gen=false is opted out",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+k8s:register-gen=false"},
|
||||
typeMembers: externalTypeMeta,
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
{
|
||||
name: "opt-out wins over +groupName",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+groupName=foo.k8s.io", "+k8s:register-gen=false"},
|
||||
typeMembers: externalTypeMeta,
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
{
|
||||
name: "no +groupName and no +k8s:register-gen tag is skipped",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
typeMembers: externalTypeMeta,
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
{
|
||||
// isInternal returns an error when the package has no TypeMeta-
|
||||
// bearing types at all, and GetTargets treats that error as a skip.
|
||||
name: "+groupName but no TypeMeta types is skipped (isInternal errors)",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+groupName=foo.k8s.io"},
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
{
|
||||
name: "internal pkg (TypeMeta without json tag) is skipped",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+groupName=foo"},
|
||||
typeMembers: internalTypeMeta,
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
{
|
||||
name: "pkg with existing register.go is skipped",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{"+groupName=foo.k8s.io"},
|
||||
typeMembers: externalTypeMeta,
|
||||
needRegisterFile: true,
|
||||
}},
|
||||
wantPaths: nil,
|
||||
},
|
||||
// Ecosystem regression: a third-party generator's tag in the same
|
||||
// doc.go must NOT cause register-gen to fail. The +groupName= still
|
||||
// activates as expected.
|
||||
{
|
||||
name: "foreign third-party generator tag is ignored",
|
||||
pkgs: []pkgSpec{{
|
||||
path: "k8s.io/api/foo/v1",
|
||||
comments: []string{
|
||||
"+k8s:my-custom-gen=value",
|
||||
"+groupName=foo.k8s.io",
|
||||
},
|
||||
typeMembers: externalTypeMeta,
|
||||
}},
|
||||
wantPaths: []string{"k8s.io/api/foo/v1"},
|
||||
wantGroups: map[string]string{"k8s.io/api/foo/v1": "foo.k8s.io"},
|
||||
wantHasType: map[string]bool{"k8s.io/api/foo/v1": true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
universe := types.Universe{}
|
||||
ctx := &generator.Context{
|
||||
Universe: universe,
|
||||
}
|
||||
for i, ps := range tc.pkgs {
|
||||
dir := ps.dir
|
||||
if dir == "" {
|
||||
dir = t.TempDir()
|
||||
}
|
||||
if ps.needRegisterFile {
|
||||
f, err := os.Create(filepath.Join(dir, "register.go"))
|
||||
if err != nil {
|
||||
t.Fatalf("creating register.go for pkg[%d]: %v", i, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
p := universe.Package(ps.path)
|
||||
p.Name = filepath.Base(ps.path)
|
||||
p.Dir = dir
|
||||
p.Comments = ps.comments
|
||||
if ps.typeMembers != nil {
|
||||
p.Types["MyType"] = &types.Type{
|
||||
Name: types.Name{Package: ps.path, Name: "MyType"},
|
||||
Kind: types.Struct,
|
||||
Members: ps.typeMembers,
|
||||
}
|
||||
}
|
||||
ctx.Inputs = append(ctx.Inputs, ps.path)
|
||||
}
|
||||
|
||||
a := &args.Args{OutputFile: "zz_generated.register.go"}
|
||||
got := GetTargets(ctx, a)
|
||||
|
||||
gotPaths := make([]string, 0, len(got))
|
||||
for _, g := range got {
|
||||
gotPaths = append(gotPaths, g.Path())
|
||||
}
|
||||
sort.Strings(gotPaths)
|
||||
wantPaths := append([]string(nil), tc.wantPaths...)
|
||||
sort.Strings(wantPaths)
|
||||
if !equalStrSlice(gotPaths, wantPaths) {
|
||||
t.Fatalf("target paths: got %v, want %v", gotPaths, wantPaths)
|
||||
}
|
||||
|
||||
// Verify per-package group and typesToGenerate by exercising the
|
||||
// generator function the SimpleTarget would run.
|
||||
for _, tgt := range got {
|
||||
st, ok := tgt.(*generator.SimpleTarget)
|
||||
if !ok {
|
||||
t.Fatalf("%s: target is %T, want *generator.SimpleTarget", tgt.Path(), tgt)
|
||||
}
|
||||
gens := st.GeneratorsFunc(ctx)
|
||||
if len(gens) != 1 {
|
||||
t.Fatalf("%s: got %d generators, want 1", tgt.Path(), len(gens))
|
||||
}
|
||||
rg, ok := gens[0].(*registerExternalGenerator)
|
||||
if !ok {
|
||||
t.Fatalf("%s: generator is %T, want *registerExternalGenerator", tgt.Path(), gens[0])
|
||||
}
|
||||
if want, ok := tc.wantGroups[tgt.Path()]; ok {
|
||||
if string(rg.gv.Group) != want {
|
||||
t.Errorf("%s: group = %q, want %q", tgt.Path(), rg.gv.Group, want)
|
||||
}
|
||||
}
|
||||
if want, ok := tc.wantHasType[tgt.Path()]; ok {
|
||||
hasType := len(rg.typesToGenerate) > 0
|
||||
if hasType != want {
|
||||
t.Errorf("%s: hasType = %v, want %v (typesToGenerate=%v)", tgt.Path(), hasType, want, rg.typesToGenerate)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func equalStrSlice(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -15,5 +15,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// +k8s:register-gen=simpletype
|
||||
// +k8s:deepcopy-gen=false
|
||||
|
||||
package v1
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/code-generator/cmd/validation-gen/validators"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/namer"
|
||||
|
|
@ -96,6 +97,8 @@ type Args struct {
|
|||
// fields apiVersion, kind, path, errorType, origin (use "*" to wildcard
|
||||
// kind/path/errorType/origin) plus a required reason.
|
||||
TestAllowlist string
|
||||
|
||||
apidefinitions.LintArgs
|
||||
}
|
||||
|
||||
// AddFlags add the generator flags to the flag set.
|
||||
|
|
@ -114,6 +117,7 @@ func (args *Args) AddFlags(fs *pflag.FlagSet) {
|
|||
"prefix prepended to every emitted test fixture filename; useful for marking files via a linguist-generated gitattributes pattern (e.g. \"zz_generated.\")")
|
||||
fs.StringVar(&args.TestAllowlist, "test-allowlist", "",
|
||||
"path to a YAML config file of rule-level filters to exclude from coverage fixture generation; only meaningful with --test-output-root")
|
||||
apidefinitions.AddFlags(&args.LintArgs, fs)
|
||||
}
|
||||
|
||||
// Validate checks the given arguments.
|
||||
|
|
@ -128,6 +132,9 @@ func (args *Args) Validate() error {
|
|||
return fmt.Errorf("--test-output-file-prefix is only meaningful with --test-output-root")
|
||||
}
|
||||
|
||||
if err := apidefinitions.ValidateFlags(args.LintRules); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/code-generator/cmd/validation-gen/validators"
|
||||
"k8s.io/code-generator/pkg/apidefinitions"
|
||||
"k8s.io/gengo/v2"
|
||||
"k8s.io/gengo/v2/codetags"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
|
|
@ -37,7 +38,6 @@ import (
|
|||
// These are the comment tags that carry parameters for validation generation.
|
||||
const (
|
||||
mainTagName = "k8s:validation-gen" // defines which types to generate validation for
|
||||
inputTagName = "k8s:validation-gen-input" // indicates that input types are in a different package
|
||||
schemeRegistryTagName = "k8s:validation-gen-scheme-registry" // defaults to k8s.io/apimachinery/pkg.runtime.Scheme
|
||||
testFixtureTagName = "k8s:validation-gen-test-fixture" // if set, generate go test files for test fixtures. Supported values: "validateFalse".
|
||||
|
||||
|
|
@ -74,40 +74,17 @@ func extractAndParseTag(tagName string, comments []string) ([]codetags.Tag, erro
|
|||
return tags, nil
|
||||
}
|
||||
|
||||
func extractMainTag(comments []string) ([]string, bool) {
|
||||
// TODO: convert to extractAndParseTag() and update all callers to use quoted values
|
||||
tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{mainTagName}, comments)
|
||||
// validationTypeMatch returns the +k8s:validation-gen tag values for pkg,
|
||||
// or false if validation-gen should not run.
|
||||
func validationTypeMatch(pkg *types.Package, idOpts []apidefinitions.Option) ([]string, bool) {
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Validation, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed to extract tags: %v", err)
|
||||
klog.Fatal(err)
|
||||
}
|
||||
values, found := tags[mainTagName]
|
||||
if !found || len(values) == 0 {
|
||||
if !info.ShouldGenerate() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
result := make([]string, len(values))
|
||||
for i, tag := range values {
|
||||
result[i] = tag.Value
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func extractInputTag(comments []string) []string {
|
||||
// TODO: convert to extractAndParseTag() and update all callers to use quoted values
|
||||
tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{inputTagName}, comments)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed to extract input tags: %v", err)
|
||||
}
|
||||
values, found := tags[inputTagName]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]string, len(values))
|
||||
for i, tag := range values {
|
||||
result[i] = tag.Value
|
||||
}
|
||||
return result
|
||||
return info.TypeFilters(), true
|
||||
}
|
||||
|
||||
// TODO: this can just accept a single bool
|
||||
|
|
@ -240,7 +217,12 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
klog.Fatalf("Failed loading boilerplate: %v", err)
|
||||
}
|
||||
|
||||
var targets []generator.Target
|
||||
var idOpts []apidefinitions.Option
|
||||
if len(args.LintRules) > 0 {
|
||||
idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...))
|
||||
}
|
||||
|
||||
var targetList []generator.Target
|
||||
|
||||
// First load other "input" packages. We do this as a single call because
|
||||
// it is MUCH faster.
|
||||
|
|
@ -248,23 +230,20 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
pkgToInput := map[string]string{}
|
||||
for _, input := range context.Inputs {
|
||||
klog.V(4).Infof("considering pkg %q", input)
|
||||
|
||||
pkg := context.Universe[input]
|
||||
|
||||
// if the types are not in the same package where the validation
|
||||
// functions are to be emitted
|
||||
inputTags := extractInputTag(pkg.Comments)
|
||||
if len(inputTags) > 1 {
|
||||
panic(fmt.Sprintf("there may only be one input tag, got %#v", inputTags))
|
||||
info, err := apidefinitions.Identify(pkg, apidefinitions.Validation, idOpts...)
|
||||
if err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
if !info.ShouldGenerate() {
|
||||
continue
|
||||
}
|
||||
if len(inputTags) == 1 {
|
||||
inputPath := inputTags[0]
|
||||
if strings.HasPrefix(inputPath, "./") || strings.HasPrefix(inputPath, "../") {
|
||||
// this is a relative dir, which will not work under gomodules.
|
||||
// join with the local package path, but warn
|
||||
klog.Fatalf("relative path (%s=%s) is not supported; use full package path (as used by 'import') instead", inputTagName, inputPath)
|
||||
}
|
||||
|
||||
// +k8s:validation-gen-input may direct the generator at types in
|
||||
// a different package than the one where validators will be emitted.
|
||||
inputPath := info.ExternalTypes()
|
||||
if inputPath != pkg.Path {
|
||||
klog.V(4).Infof(" input pkg %v", inputPath)
|
||||
inputPkgs = append(inputPkgs, inputPath)
|
||||
pkgToInput[input] = inputPath
|
||||
|
|
@ -332,7 +311,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
|
||||
schemeRegistry := schemeRegistryTag(pkg)
|
||||
|
||||
typesWith, found := extractMainTag(pkg.Comments)
|
||||
typesWith, found := validationTypeMatch(pkg, idOpts)
|
||||
if !found {
|
||||
klog.V(2).InfoS(" did not find required tag", "tag", mainTagName)
|
||||
continue
|
||||
|
|
@ -412,7 +391,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
}
|
||||
}
|
||||
|
||||
targets = append(targets,
|
||||
targetList = append(targetList,
|
||||
&generator.SimpleTarget{
|
||||
PkgName: pkg.Name,
|
||||
PkgPath: pkg.Path,
|
||||
|
|
@ -450,7 +429,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
if err != nil {
|
||||
klog.Fatalf("loading allowlist: %v", err)
|
||||
}
|
||||
targets = append(targets, testTargets(args.TestOutputRoot, args.TestOutputFilePrefix, groupKindReports, allowlist, boilerplate)...)
|
||||
targetList = append(targetList, testTargets(args.TestOutputRoot, args.TestOutputFilePrefix, groupKindReports, allowlist, boilerplate)...)
|
||||
|
||||
if len(linter.lintErrors) > 0 {
|
||||
buf := strings.Builder{}
|
||||
|
|
@ -463,7 +442,7 @@ func GetTargets(context *generator.Context, args *Args) []generator.Target {
|
|||
}
|
||||
klog.Fatalf("lint failed:\n%s", buf.String())
|
||||
}
|
||||
return targets
|
||||
return targetList
|
||||
}
|
||||
|
||||
func isTypeWith(t *types.Type, typesWith []string) bool {
|
||||
|
|
|
|||
Loading…
Reference in a new issue