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