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