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