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 := ©MethodMaker{ 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 }