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