sigs.k8s.io/controller-tools@v0.15.1-0.20240515195456-85686cb69316/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  
   180  %[3]s
   181  
   182  // Code generated by controller-gen. DO NOT EDIT.
   183  
   184  package %[1]s
   185  
   186  import (
   187  %[2]s
   188  )
   189  
   190  `, packageName, strings.Join(imports.ImportSpecs(), "\n"), headerText)
   191  	if err != nil {
   192  		pkg.AddError(err)
   193  	}
   194  }
   195  
   196  // generateForPackage generates DeepCopy and runtime.Object implementations for
   197  // types in the given package, writing the formatted result to given writer.
   198  // May return nil if source could not be generated.
   199  func (ctx *ObjectGenCtx) generateForPackage(root *loader.Package) []byte {
   200  	allTypes, err := enabledOnPackage(ctx.Collector, root)
   201  	if err != nil {
   202  		root.AddError(err)
   203  		return nil
   204  	}
   205  
   206  	ctx.Checker.Check(root)
   207  
   208  	root.NeedTypesInfo()
   209  
   210  	byType := make(map[string][]byte)
   211  	imports := &importsList{
   212  		byPath:  make(map[string]string),
   213  		byAlias: make(map[string]string),
   214  		pkg:     root,
   215  	}
   216  	// avoid confusing aliases by "reserving" the root package's name as an alias
   217  	imports.byAlias[root.Name] = ""
   218  
   219  	if err := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) {
   220  		outContent := new(bytes.Buffer)
   221  
   222  		// copy when nabled for all types and not disabled, or enabled
   223  		// specifically on this type
   224  		if !enabledOnType(allTypes, info) {
   225  			return
   226  		}
   227  
   228  		// avoid copying non-exported types, etc
   229  		if !shouldBeCopied(root, info) {
   230  			return
   231  		}
   232  
   233  		copyCtx := &copyMethodMaker{
   234  			pkg:         root,
   235  			importsList: imports,
   236  			codeWriter:  &codeWriter{out: outContent},
   237  		}
   238  
   239  		copyCtx.GenerateMethodsFor(root, info)
   240  
   241  		outBytes := outContent.Bytes()
   242  		if len(outBytes) > 0 {
   243  			byType[info.Name] = outBytes
   244  		}
   245  	}); err != nil {
   246  		root.AddError(err)
   247  		return nil
   248  	}
   249  
   250  	if len(byType) == 0 {
   251  		return nil
   252  	}
   253  
   254  	outContent := new(bytes.Buffer)
   255  	writeHeader(root, outContent, root.Name, imports, ctx.HeaderText)
   256  	writeMethods(root, outContent, byType)
   257  
   258  	outBytes := outContent.Bytes()
   259  	formattedBytes, err := format.Source(outBytes)
   260  	if err != nil {
   261  		root.AddError(err)
   262  		// we still write the invalid source to disk to figure out what went wrong
   263  	} else {
   264  		outBytes = formattedBytes
   265  	}
   266  
   267  	return outBytes
   268  }
   269  
   270  // writeMethods writes each method to the file, sorted by type name.
   271  func writeMethods(pkg *loader.Package, out io.Writer, byType map[string][]byte) {
   272  	sortedNames := make([]string, 0, len(byType))
   273  	for name := range byType {
   274  		sortedNames = append(sortedNames, name)
   275  	}
   276  	sort.Strings(sortedNames)
   277  
   278  	for _, name := range sortedNames {
   279  		_, err := out.Write(byType[name])
   280  		if err != nil {
   281  			pkg.AddError(err)
   282  		}
   283  	}
   284  }
   285  
   286  // writeFormatted outputs the given code, after gofmt-ing it.  If we couldn't gofmt,
   287  // we write the unformatted code for debugging purposes.
   288  func writeOut(ctx *genall.GenerationContext, root *loader.Package, outBytes []byte) {
   289  	outputFile, err := ctx.Open(root, "zz_generated.deepcopy.go")
   290  	if err != nil {
   291  		root.AddError(err)
   292  		return
   293  	}
   294  	defer outputFile.Close()
   295  	n, err := outputFile.Write(outBytes)
   296  	if err != nil {
   297  		root.AddError(err)
   298  		return
   299  	}
   300  	if n < len(outBytes) {
   301  		root.AddError(io.ErrShortWrite)
   302  	}
   303  }