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