github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/pkg/deepcopy/gen.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 deepcopy
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"go/ast"
    23  	"go/format"
    24  	"io"
    25  	"sort"
    26  	"strings"
    27  
    28  	"sigs.k8s.io/controller-tools/pkg/genall"
    29  	"sigs.k8s.io/controller-tools/pkg/loader"
    30  	"sigs.k8s.io/controller-tools/pkg/markers"
    31  )
    32  
    33  // NB(directxman12): markers.LoadRoots ignores autogenerated code via a build tag
    34  // so any time we check for existing deepcopy functions, we only seen manually written ones.
    35  
    36  const (
    37  	runtimeObjPath = "k8s.io/apimachinery/pkg/runtime.Object"
    38  )
    39  
    40  var (
    41  	enablePkgMarker  = markers.Must(markers.MakeDefinition("kubebuilder:object:generate", markers.DescribesPackage, false))
    42  	enableTypeMarker = markers.Must(markers.MakeDefinition("kubebuilder:object:generate", markers.DescribesType, false))
    43  	isObjectMarker   = markers.Must(markers.MakeDefinition("kubebuilder:object:root", markers.DescribesType, false))
    44  
    45  	legacyEnablePkgMarker  = markers.Must(markers.MakeDefinition("k8s:deepcopy-gen", markers.DescribesPackage, markers.RawArguments(nil)))
    46  	legacyEnableTypeMarker = markers.Must(markers.MakeDefinition("k8s:deepcopy-gen", markers.DescribesType, markers.RawArguments(nil)))
    47  	legacyIsObjectMarker   = markers.Must(markers.MakeDefinition("k8s:deepcopy-gen:interfaces", markers.DescribesType, ""))
    48  )
    49  
    50  // +controllertools:marker:generateHelp
    51  
    52  // Generator generates code containing DeepCopy, DeepCopyInto, and
    53  // DeepCopyObject method implementations.
    54  type Generator struct {
    55  	// HeaderFile specifies the header text (e.g. license) to prepend to generated files.
    56  	HeaderFile string `marker:",optional"`
    57  	// Year specifies the year to substitute for " YEAR" in the header file.
    58  	Year string `marker:",optional"`
    59  }
    60  
    61  func (Generator) CheckFilter() loader.NodeFilter {
    62  	return func(node ast.Node) bool {
    63  		// ignore interfaces
    64  		_, isIface := node.(*ast.InterfaceType)
    65  		return !isIface
    66  	}
    67  }
    68  
    69  func (Generator) RegisterMarkers(into *markers.Registry) error {
    70  	if err := markers.RegisterAll(into,
    71  		enablePkgMarker, legacyEnablePkgMarker, enableTypeMarker,
    72  		legacyEnableTypeMarker, isObjectMarker, legacyIsObjectMarker); err != nil {
    73  		return err
    74  	}
    75  	into.AddHelp(enablePkgMarker,
    76  		markers.SimpleHelp("object", "enables or disables object interface & deepcopy implementation generation for this package"))
    77  	into.AddHelp(
    78  		enableTypeMarker, markers.SimpleHelp("object", "overrides enabling or disabling deepcopy generation for this type"))
    79  	into.AddHelp(isObjectMarker,
    80  		markers.SimpleHelp("object", "enables object interface implementation generation for this type"))
    81  
    82  	into.AddHelp(legacyEnablePkgMarker,
    83  		markers.DeprecatedHelp(enablePkgMarker.Name, "object", "enables or disables object interface & deepcopy implementation generation for this package"))
    84  	into.AddHelp(legacyEnableTypeMarker,
    85  		markers.DeprecatedHelp(enableTypeMarker.Name, "object", "overrides enabling or disabling deepcopy generation for this type"))
    86  	into.AddHelp(legacyIsObjectMarker,
    87  		markers.DeprecatedHelp(isObjectMarker.Name, "object", "enables object interface implementation generation for this type"))
    88  	return nil
    89  }
    90  
    91  func enabledOnPackage(col *markers.Collector, pkg *loader.Package) (bool, error) {
    92  	pkgMarkers, err := markers.PackageMarkers(col, pkg)
    93  	if err != nil {
    94  		return false, err
    95  	}
    96  	pkgMarker := pkgMarkers.Get(enablePkgMarker.Name)
    97  	if pkgMarker != nil {
    98  		return pkgMarker.(bool), nil
    99  	}
   100  	legacyMarker := pkgMarkers.Get(legacyEnablePkgMarker.Name)
   101  	if legacyMarker != nil {
   102  		legacyMarkerVal := string(legacyMarker.(markers.RawArguments))
   103  		firstArg := strings.Split(legacyMarkerVal, ",")[0]
   104  		return firstArg == "package", nil
   105  	}
   106  
   107  	return false, nil
   108  }
   109  
   110  func enabledOnType(allTypes bool, info *markers.TypeInfo) bool {
   111  	if typeMarker := info.Markers.Get(enableTypeMarker.Name); typeMarker != nil {
   112  		return typeMarker.(bool)
   113  	}
   114  	legacyMarker := info.Markers.Get(legacyEnableTypeMarker.Name)
   115  	if legacyMarker != nil {
   116  		legacyMarkerVal := string(legacyMarker.(markers.RawArguments))
   117  		return legacyMarkerVal == "true"
   118  	}
   119  	return allTypes || genObjectInterface(info)
   120  }
   121  
   122  func genObjectInterface(info *markers.TypeInfo) bool {
   123  	objectEnabled := info.Markers.Get(isObjectMarker.Name)
   124  	if objectEnabled != nil {
   125  		return objectEnabled.(bool)
   126  	}
   127  
   128  	for _, legacyEnabled := range info.Markers[legacyIsObjectMarker.Name] {
   129  		if legacyEnabled == runtimeObjPath {
   130  			return true
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  func (d Generator) Generate(ctx *genall.GenerationContext) error {
   137  	var headerText string
   138  
   139  	if d.HeaderFile != "" {
   140  		headerBytes, err := ctx.ReadFile(d.HeaderFile)
   141  		if err != nil {
   142  			return err
   143  		}
   144  		headerText = string(headerBytes)
   145  	}
   146  	headerText = strings.ReplaceAll(headerText, " YEAR", " "+d.Year)
   147  
   148  	objGenCtx := ObjectGenCtx{
   149  		Collector:  ctx.Collector,
   150  		Checker:    ctx.Checker,
   151  		HeaderText: headerText,
   152  	}
   153  
   154  	for _, root := range ctx.Roots {
   155  		outContents := objGenCtx.generateForPackage(root)
   156  		if outContents == nil {
   157  			continue
   158  		}
   159  
   160  		writeOut(ctx, root, outContents)
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // ObjectGenCtx contains the common info for generating deepcopy implementations.
   167  // It mostly exists so that generating for a package can be easily tested without
   168  // requiring a full set of output rules, etc.
   169  type ObjectGenCtx struct {
   170  	Collector  *markers.Collector
   171  	Checker    *loader.TypeChecker
   172  	HeaderText string
   173  }
   174  
   175  // writeHeader writes out the build tag, package declaration, and imports
   176  func writeHeader(pkg *loader.Package, out io.Writer, packageName string, imports *importsList, headerText string) {
   177  	// NB(directxman12): blank line after build tags to distinguish them from comments
   178  	_, err := fmt.Fprintf(out, `//go:build !ignore_autogenerated
   179  // +build !ignore_autogenerated
   180  
   181  %[3]s
   182  
   183  // Code generated by controller-gen. DO NOT EDIT.
   184  
   185  package %[1]s
   186  
   187  import (
   188  %[2]s
   189  )
   190  
   191  `, packageName, strings.Join(imports.ImportSpecs(), "\n"), headerText)
   192  	if err != nil {
   193  		pkg.AddError(err)
   194  	}
   195  
   196  }
   197  
   198  // generateForPackage generates DeepCopy and runtime.Object implementations for
   199  // types in the given package, writing the formatted result to given writer.
   200  // May return nil if source could not be generated.
   201  func (ctx *ObjectGenCtx) generateForPackage(root *loader.Package) []byte {
   202  	allTypes, err := enabledOnPackage(ctx.Collector, root)
   203  	if err != nil {
   204  		root.AddError(err)
   205  		return nil
   206  	}
   207  
   208  	ctx.Checker.Check(root)
   209  
   210  	root.NeedTypesInfo()
   211  
   212  	byType := make(map[string][]byte)
   213  	imports := &importsList{
   214  		byPath:  make(map[string]string),
   215  		byAlias: make(map[string]string),
   216  		pkg:     root,
   217  	}
   218  	// avoid confusing aliases by "reserving" the root package's name as an alias
   219  	imports.byAlias[root.Name] = ""
   220  
   221  	if err := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) {
   222  		outContent := new(bytes.Buffer)
   223  
   224  		// copy when nabled for all types and not disabled, or enabled
   225  		// specifically on this type
   226  		if !enabledOnType(allTypes, info) {
   227  			return
   228  		}
   229  
   230  		// avoid copying non-exported types, etc
   231  		if !shouldBeCopied(root, info) {
   232  			return
   233  		}
   234  
   235  		copyCtx := &copyMethodMaker{
   236  			pkg:         root,
   237  			importsList: imports,
   238  			codeWriter:  &codeWriter{out: outContent},
   239  		}
   240  
   241  		copyCtx.GenerateMethodsFor(root, info)
   242  
   243  		outBytes := outContent.Bytes()
   244  		if len(outBytes) > 0 {
   245  			byType[info.Name] = outBytes
   246  		}
   247  	}); err != nil {
   248  		root.AddError(err)
   249  		return nil
   250  	}
   251  
   252  	if len(byType) == 0 {
   253  		return nil
   254  	}
   255  
   256  	outContent := new(bytes.Buffer)
   257  	writeHeader(root, outContent, root.Name, imports, ctx.HeaderText)
   258  	writeMethods(root, outContent, byType)
   259  
   260  	outBytes := outContent.Bytes()
   261  	formattedBytes, err := format.Source(outBytes)
   262  	if err != nil {
   263  		root.AddError(err)
   264  		// we still write the invalid source to disk to figure out what went wrong
   265  	} else {
   266  		outBytes = formattedBytes
   267  	}
   268  
   269  	return outBytes
   270  }
   271  
   272  // writeMethods writes each method to the file, sorted by type name.
   273  func writeMethods(pkg *loader.Package, out io.Writer, byType map[string][]byte) {
   274  	sortedNames := make([]string, 0, len(byType))
   275  	for name := range byType {
   276  		sortedNames = append(sortedNames, name)
   277  	}
   278  	sort.Strings(sortedNames)
   279  
   280  	for _, name := range sortedNames {
   281  		_, err := out.Write(byType[name])
   282  		if err != nil {
   283  			pkg.AddError(err)
   284  		}
   285  	}
   286  }
   287  
   288  // writeFormatted outputs the given code, after gofmt-ing it.  If we couldn't gofmt,
   289  // we write the unformatted code for debugging purposes.
   290  func writeOut(ctx *genall.GenerationContext, root *loader.Package, outBytes []byte) {
   291  	outputFile, err := ctx.Open(root, "zz_generated.deepcopy.go")
   292  	if err != nil {
   293  		root.AddError(err)
   294  		return
   295  	}
   296  	defer outputFile.Close()
   297  	n, err := outputFile.Write(outBytes)
   298  	if err != nil {
   299  		root.AddError(err)
   300  		return
   301  	}
   302  	if n < len(outBytes) {
   303  		root.AddError(io.ErrShortWrite)
   304  	}
   305  }