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