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