Remove old conversion generator

pull/6/head
Wojciech Tyczynski 2016-04-14 20:42:13 +02:00
parent 490c68dbe3
commit 89e860e622
11 changed files with 31 additions and 1308 deletions

View File

@ -1,132 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"fmt"
"io"
"os"
"path"
"runtime"
"k8s.io/kubernetes/pkg/api"
_ "k8s.io/kubernetes/pkg/api/install"
"k8s.io/kubernetes/pkg/api/unversioned"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/componentconfig/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/metrics/install"
kruntime "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
"github.com/golang/glog"
flag "github.com/spf13/pflag"
"golang.org/x/tools/imports"
)
const pkgBase = "k8s.io/kubernetes/pkg"
var (
functionDest = flag.StringP("funcDest", "f", "-", "Output for conversion functions; '-' means stdout")
groupVersion = flag.StringP("version", "v", "api/v1", "groupPath/version for conversion.")
)
// We're moving to pkg/apis/group/version. This handles new and legacy packages.
func pkgPath(group, version string) string {
if group == "" {
group = "api"
}
gv := group
if version != "" {
gv = path.Join(group, version)
}
switch {
case group == "api":
// TODO(lavalamp): remove this special case when we move api to apis/api
return path.Join(pkgBase, gv)
default:
return path.Join(pkgBase, "apis", gv)
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.Parse()
var funcOut io.Writer
if *functionDest == "-" {
funcOut = os.Stdout
} else {
file, err := os.Create(*functionDest)
if err != nil {
glog.Fatalf("Couldn't open %v: %v", *functionDest, err)
}
defer file.Close()
funcOut = file
}
data := new(bytes.Buffer)
gv, err := unversioned.ParseGroupVersion(*groupVersion)
if err != nil {
glog.Fatalf("Error parsing groupversion %v: %v", *groupVersion, err)
}
_, err = data.WriteString(fmt.Sprintf("package %v\n", gv.Version))
if err != nil {
glog.Fatalf("Error while writing package line: %v", err)
}
versionPath := pkgPath(gv.Group, gv.Version)
generator := kruntime.NewConversionGenerator(api.Scheme, versionPath)
apiShort := generator.AddImport(path.Join(pkgBase, "api"))
generator.AddImport(path.Join(pkgBase, "api/resource"))
generator.AddImport(path.Join(pkgBase, "types"))
// TODO(wojtek-t): Change the overwrites to a flag.
generator.OverwritePackage(gv.Version, "")
for _, knownType := range api.Scheme.KnownTypes(gv) {
if knownType.PkgPath() != versionPath {
continue
}
if err := generator.GenerateConversionsForType(gv, knownType); err != nil {
glog.Errorf("Error while generating conversion functions for %v: %v", knownType, err)
}
}
generator.RepackImports(sets.NewString())
if err := generator.WriteImports(data); err != nil {
glog.Fatalf("Error while writing imports: %v", err)
}
if err := generator.WriteConversionFunctions(data); err != nil {
glog.Fatalf("Error while writing conversion functions: %v", err)
}
if err := generator.RegisterConversionFunctions(data, fmt.Sprintf("%s.Scheme", apiShort)); err != nil {
glog.Fatalf("Error while writing conversion functions: %v", err)
}
b, err := imports.Process("", data.Bytes(), nil)
if err != nil {
for i, s := range bytes.Split(data.Bytes(), []byte("\n")) {
glog.Infof("%d:\t%s", i, s)
}
glog.Fatalf("Error while update imports: %v\n", err)
}
if _, err := funcOut.Write(b); err != nil {
glog.Fatalf("Error while writing out the resulting file: %v", err)
}
}

View File

@ -1,52 +0,0 @@
#!/bin/bash
# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::setup_env
genconversion=$(kube::util::find-binary "genconversion")
function generate_version() {
local group_version=$1
local TMPFILE="/tmp/conversion_generated.$(date +%s).go"
echo "Generating for ${group_version}"
sed 's/YEAR/2015/' hack/boilerplate/boilerplate.go.txt > "$TMPFILE"
cat >> "$TMPFILE" <<EOF
// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY \$KUBEROOT/hack/update-generated-conversions.sh
EOF
"${genconversion}" -v "${group_version}" -f - >> "$TMPFILE"
mv "$TMPFILE" "pkg/$(kube::util::group-version-to-pkg-path "${group_version}")/conversion_generated.go"
}
# TODO(lavalamp): get this list by listing the pkg/apis/ directory?
DEFAULT_GROUP_VERSIONS=""
VERSIONS=${VERSIONS:-$DEFAULT_GROUP_VERSIONS}
for ver in $VERSIONS; do
# Ensure that the version being processed is registered by setting
# KUBE_API_VERSIONS.
KUBE_API_VERSIONS="${ver}" generate_version "${ver}"
done

View File

@ -1,53 +0,0 @@
#!/bin/bash
# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::setup_env
APIROOTS=${APIROOTS:-pkg/apis/authorization pkg/apis/autoscaling pkg/apis/batch pkg/apis/extensions pkg/apis/metrics}
_tmp="${KUBE_ROOT}/_tmp"
cleanup() {
rm -rf "${_tmp}"
}
trap "cleanup" EXIT SIGINT
for APIROOT in ${APIROOTS}; do
mkdir -p "${_tmp}/${APIROOT%/*}"
cp -a "${KUBE_ROOT}/${APIROOT}" "${_tmp}/${APIROOT}"
done
"${KUBE_ROOT}/hack/after-build/update-generated-conversions.sh"
for APIROOT in ${APIROOTS}; do
TMP_APIROOT="${_tmp}/${APIROOT}"
echo "diffing ${APIROOT} against freshly generated conversions"
ret=0
diff -Naupr -I 'Auto generated by' "${KUBE_ROOT}/${APIROOT}" "${TMP_APIROOT}" || ret=$?
cp -a "${TMP_APIROOT}" "${KUBE_ROOT}/${APIROOT%/*}"
if [[ $ret -eq 0 ]]; then
echo "${APIROOT} up to date."
else
echo "${APIROOT} is out of date. Please run hack/update-generated-conversions.sh"
exit 1
fi
done

View File

@ -103,7 +103,6 @@ kube::golang::test_targets() {
cmd/genman
cmd/mungedocs
cmd/genbashcomp
cmd/genconversion
cmd/genswaggertypedocs
examples/k8petstore/web-server/src
github.com/onsi/ginkgo/ginkgo

View File

@ -51,7 +51,6 @@ if ! $ALL ; then
fi
BASH_TARGETS="codecgen
generated-conversions
generated-docs
generated-swagger-docs
swagger-spec

View File

@ -1,30 +0,0 @@
#!/bin/bash
# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::setup_env
"${KUBE_ROOT}/hack/build-go.sh" cmd/genconversion
"${KUBE_ROOT}/hack/after-build/update-generated-conversions.sh" "$@"
# ex: ts=2 sw=2 et filetype=sh

View File

@ -1,30 +0,0 @@
#!/bin/bash
# Copyright 2015 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::setup_env
"${KUBE_ROOT}/hack/build-go.sh" cmd/genconversion
"${KUBE_ROOT}/hack/after-build/verify-generated-conversions.sh" "$@"
# ex: ts=2 sw=2 et filetype=sh

View File

@ -134,18 +134,6 @@ else
fi
echo "${reset}"
echo -ne "Checking for conversions that need updating... "
if ! hack/after-build/verify-generated-conversions.sh > /dev/null; then
echo "${red}ERROR!"
echo "Some conversions functions need regeneration."
echo "To regenerate conversions, run:"
echo " hack/update-generated-conversions.sh"
exit_code=1
else
echo "${green}OK"
fi
echo "${reset}"
echo -ne "Checking for swagger type documentation that need updating... "
if ! hack/after-build/verify-generated-swagger-docs.sh > /dev/null; then
echo "${red}ERROR!"

View File

@ -1,921 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package runtime
import (
"fmt"
"io"
"log"
"path"
"reflect"
goruntime "runtime"
"sort"
"strings"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util/sets"
)
type ConversionGenerator interface {
GenerateConversionsForType(groupVersion unversioned.GroupVersion, reflection reflect.Type) error
WriteConversionFunctions(w io.Writer) error
RegisterConversionFunctions(w io.Writer, pkg string) error
AddImport(pkg string) string
RepackImports(exclude sets.String)
WriteImports(w io.Writer) error
OverwritePackage(pkg, overwrite string)
AssumePrivateConversions()
}
func NewConversionGenerator(scheme *Scheme, targetPkg string) ConversionGenerator {
g := &conversionGenerator{
scheme: scheme,
nameFormat: "Convert_%s_%s_To_%s_%s",
generatedNamePrefix: "auto",
targetPkg: targetPkg,
publicFuncs: make(map[typePair]functionName),
convertibles: make(map[reflect.Type]reflect.Type),
overridden: make(map[reflect.Type]bool),
pkgOverwrites: make(map[string]string),
imports: make(map[string]string),
shortImports: make(map[string]string),
}
g.targetPackage(targetPkg)
g.AddImport("reflect")
g.AddImport("k8s.io/kubernetes/pkg/conversion")
return g
}
var complexTypes []reflect.Kind = []reflect.Kind{reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct}
type functionName struct {
name string
packageName string
}
type conversionGenerator struct {
scheme *Scheme
nameFormat string
generatedNamePrefix string
targetPkg string
publicFuncs map[typePair]functionName
convertibles map[reflect.Type]reflect.Type
overridden map[reflect.Type]bool
// If pkgOverwrites is set for a given package name, that package name
// will be replaced while writing conversion function. If empty, package
// name will be omitted.
pkgOverwrites map[string]string
// map of package names to shortname
imports map[string]string
// map of short names to package names
shortImports map[string]string
// A buffer that is used for storing lines that needs to be written.
linesToPrint []string
// if true, we assume conversions on the scheme are not available to us in the current package
assumePrivateConversions bool
}
func (g *conversionGenerator) AssumePrivateConversions() {
g.assumePrivateConversions = true
}
func (g *conversionGenerator) AddImport(pkg string) string {
return g.addImportByPath(pkg)
}
func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVersion, reflection reflect.Type) error {
kind := reflection.Name()
// TODO this is equivalent to what it did before, but it needs to be fixed for the proper group
internalVersion := gv
internalVersion.Version = APIVersionInternal
internalObj, err := g.scheme.New(internalVersion.WithKind(kind))
if err != nil {
return fmt.Errorf("cannot create an object of type %v in internal version", kind)
}
internalObjType := reflect.TypeOf(internalObj)
if internalObjType.Kind() != reflect.Ptr {
return fmt.Errorf("created object should be of type Ptr: %v", internalObjType.Kind())
}
inErr := g.generateConversionsBetween(reflection, internalObjType.Elem())
outErr := g.generateConversionsBetween(internalObjType.Elem(), reflection)
if inErr != nil || outErr != nil {
return fmt.Errorf("errors: %v, %v", inErr, outErr)
}
return nil
}
// primitiveConversion returns true if the two types can be converted via a cast.
func primitiveConversion(inType, outType reflect.Type) (string, bool) {
switch inType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
switch outType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return outType.Name(), true
}
}
return "", false
}
func (g *conversionGenerator) generateConversionsBetween(inType, outType reflect.Type) error {
existingConversion := g.scheme.Converter().HasConversionFunc(inType, outType) && g.scheme.Converter().HasConversionFunc(outType, inType)
// Avoid processing the same type multiple times.
if value, found := g.convertibles[inType]; found {
if value != outType {
return fmt.Errorf("multiple possible convertibles for %v", inType)
}
return nil
}
if inType == outType {
switch inType.Kind() {
case reflect.Ptr:
return g.generateConversionsBetween(inType.Elem(), inType.Elem())
case reflect.Struct:
// pointers to structs invoke new(inType)
g.addImportByPath(inType.PkgPath())
}
g.rememberConversionFunction(inType, inType, false)
// Don't generate conversion methods for the same type.
return nil
}
if _, ok := primitiveConversion(inType, outType); ok {
return nil
}
if inType.Kind() != outType.Kind() {
if existingConversion {
g.rememberConversionFunction(inType, outType, false)
g.rememberConversionFunction(outType, inType, false)
return nil
}
return fmt.Errorf("cannot convert types of different kinds: %v %v", inType, outType)
}
g.addImportByPath(inType.PkgPath())
g.addImportByPath(outType.PkgPath())
// We should be able to generate conversions both sides.
switch inType.Kind() {
case reflect.Map:
inErr := g.generateConversionsForMap(inType, outType)
outErr := g.generateConversionsForMap(outType, inType)
if !existingConversion && (inErr != nil || outErr != nil) {
return inErr
}
// We don't add it to g.convertibles - maps should be handled correctly
// inside appropriate conversion functions.
return nil
case reflect.Ptr:
inErr := g.generateConversionsBetween(inType.Elem(), outType.Elem())
outErr := g.generateConversionsBetween(outType.Elem(), inType.Elem())
if !existingConversion && (inErr != nil || outErr != nil) {
return inErr
}
// We don't add it to g.convertibles - maps should be handled correctly
// inside appropriate conversion functions.
return nil
case reflect.Slice:
inErr := g.generateConversionsForSlice(inType, outType)
outErr := g.generateConversionsForSlice(outType, inType)
if !existingConversion && (inErr != nil || outErr != nil) {
return inErr
}
// We don't add it to g.convertibles - slices should be handled correctly
// inside appropriate conversion functions.
return nil
case reflect.Interface:
// TODO(wojtek-t): Currently we don't support converting interfaces.
return fmt.Errorf("interfaces are not supported")
case reflect.Struct:
inErr := g.generateConversionsForStruct(inType, outType)
outErr := g.generateConversionsForStruct(outType, inType)
if !existingConversion && (inErr != nil || outErr != nil) {
return inErr
}
g.rememberConversionFunction(inType, outType, true)
if existingConversion {
g.overridden[inType] = true
}
g.convertibles[inType] = outType
return nil
default:
// All simple types should be handled correctly with default conversion.
return nil
}
}
func isComplexType(reflection reflect.Type) bool {
for _, complexType := range complexTypes {
if complexType == reflection.Kind() {
return true
}
}
return false
}
func (g *conversionGenerator) rememberConversionFunction(inType, outType reflect.Type, willGenerate bool) {
if _, ok := g.publicFuncs[typePair{inType, outType}]; ok {
return
}
if v, ok := g.scheme.Converter().ConversionFuncValue(inType, outType); ok {
if fn := goruntime.FuncForPC(v.Pointer()); fn != nil {
name := fn.Name()
var p, n string
if last := strings.LastIndex(name, "."); last != -1 {
p = name[:last]
n = name[last+1:]
} else {
n = name
}
if isPublic(n) {
g.AddImport(p)
g.publicFuncs[typePair{inType, outType}] = functionName{name: n, packageName: p}
} else {
log.Printf("WARNING: Cannot generate conversion %v -> %v, method %q is private", inType, outType, fn.Name())
}
} else {
log.Printf("WARNING: Cannot generate conversion %v -> %v, method is not accessible", inType, outType)
}
} else if willGenerate {
g.publicFuncs[typePair{inType, outType}] = functionName{name: g.conversionFunctionName(inType, outType)}
}
}
func isPublic(name string) bool {
return strings.ToUpper(name[:1]) == name[:1]
}
func (g *conversionGenerator) generateConversionsForMap(inType, outType reflect.Type) error {
inKey := inType.Key()
outKey := outType.Key()
g.addImportByPath(inKey.PkgPath())
g.addImportByPath(outKey.PkgPath())
if err := g.generateConversionsBetween(inKey, outKey); err != nil {
return err
}
inValue := inType.Elem()
outValue := outType.Elem()
g.addImportByPath(inValue.PkgPath())
g.addImportByPath(outValue.PkgPath())
if err := g.generateConversionsBetween(inValue, outValue); err != nil {
return err
}
return nil
}
func (g *conversionGenerator) generateConversionsForSlice(inType, outType reflect.Type) error {
inElem := inType.Elem()
outElem := outType.Elem()
// slice conversion requires the package for the destination type in order to instantiate the map
g.addImportByPath(outElem.PkgPath())
if err := g.generateConversionsBetween(inElem, outElem); err != nil {
return err
}
return nil
}
func (g *conversionGenerator) generateConversionsForStruct(inType, outType reflect.Type) error {
errs := []string{}
for i := 0; i < inType.NumField(); i++ {
inField := inType.Field(i)
outField, found := outType.FieldByName(inField.Name)
if !found {
// aggregate the errors so we can return them at the end but still provide
// best effort for generation for other fields in this type
errs = append(errs, fmt.Sprintf("couldn't find a corresponding field %v in %v", inField.Name, outType))
continue
}
if isComplexType(inField.Type) {
if err := g.generateConversionsBetween(inField.Type, outField.Type); err != nil {
return err
}
}
}
if len(errs) == 0 {
return nil
}
return fmt.Errorf(strings.Join(errs, ","))
}
// A buffer of lines that will be written.
type bufferedLine struct {
line string
indentation int
}
type buffer struct {
lines []bufferedLine
}
func newBuffer() *buffer {
return &buffer{
lines: make([]bufferedLine, 0),
}
}
func (b *buffer) addLine(line string, indent int) {
b.lines = append(b.lines, bufferedLine{line, indent})
}
func (b *buffer) flushLines(w io.Writer) error {
for _, line := range b.lines {
indentation := strings.Repeat("\t", line.indentation)
fullLine := fmt.Sprintf("%s%s", indentation, line.line)
if _, err := io.WriteString(w, fullLine); err != nil {
return err
}
}
return nil
}
type byName []reflect.Type
func (s byName) Len() int {
return len(s)
}
func (s byName) Less(i, j int) bool {
fullNameI := s[i].PkgPath() + "/" + s[i].Name()
fullNameJ := s[j].PkgPath() + "/" + s[j].Name()
return fullNameI < fullNameJ
}
func (s byName) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (g *conversionGenerator) targetPackage(pkg string) {
g.imports[pkg] = ""
g.shortImports[""] = pkg
}
func (g *conversionGenerator) RepackImports(exclude sets.String) {
var packages []string
for key := range g.imports {
packages = append(packages, key)
}
sort.Strings(packages)
g.imports = make(map[string]string)
g.shortImports = make(map[string]string)
g.targetPackage(g.targetPkg)
for _, pkg := range packages {
if !exclude.Has(pkg) {
g.addImportByPath(pkg)
}
}
}
func (g *conversionGenerator) WriteImports(w io.Writer) error {
var packages []string
for key := range g.imports {
packages = append(packages, key)
}
sort.Strings(packages)
buffer := newBuffer()
indent := 0
buffer.addLine("import (\n", indent)
for _, importPkg := range packages {
if len(importPkg) == 0 {
continue
}
if len(g.imports[importPkg]) == 0 {
continue
}
buffer.addLine(fmt.Sprintf("%s \"%s\"\n", g.imports[importPkg], importPkg), indent+1)
}
buffer.addLine(")\n", indent)
buffer.addLine("\n", indent)
if err := buffer.flushLines(w); err != nil {
return err
}
return nil
}
func (g *conversionGenerator) WriteConversionFunctions(w io.Writer) error {
// It's desired to print conversion functions always in the same order
// (e.g. for better tracking of what has really been added).
var keys []reflect.Type
for key := range g.convertibles {
keys = append(keys, key)
}
sort.Sort(byName(keys))
buffer := newBuffer()
indent := 0
for _, inType := range keys {
outType := g.convertibles[inType]
// All types in g.convertibles are structs.
if inType.Kind() != reflect.Struct {
return fmt.Errorf("non-struct conversions are not-supported")
}
if err := g.writeConversionForType(buffer, inType, outType, indent); err != nil {
return err
}
}
if err := buffer.flushLines(w); err != nil {
return err
}
return nil
}
func (g *conversionGenerator) writeRegisterHeader(b *buffer, pkg string, indent int) {
b.addLine("func init() {\n", indent)
b.addLine(fmt.Sprintf("err := %s.AddGeneratedConversionFuncs(\n", pkg), indent+1)
}
func (g *conversionGenerator) writeRegisterFooter(b *buffer, indent int) {
b.addLine(")\n", indent+1)
b.addLine("if err != nil {\n", indent+1)
b.addLine("// If one of the conversion functions is malformed, detect it immediately.\n", indent+2)
b.addLine("panic(err)\n", indent+2)
b.addLine("}\n", indent+1)
b.addLine("}\n", indent)
b.addLine("\n", indent)
}
func (g *conversionGenerator) RegisterConversionFunctions(w io.Writer, pkg string) error {
// Write conversion function names alphabetically ordered.
var names []string
for inType, outType := range g.convertibles {
names = append(names, g.generatedFunctionName(inType, outType))
}
sort.Strings(names)
buffer := newBuffer()
indent := 0
g.writeRegisterHeader(buffer, pkg, indent)
for _, name := range names {
buffer.addLine(fmt.Sprintf("%s,\n", name), indent+2)
}
g.writeRegisterFooter(buffer, indent)
if err := buffer.flushLines(w); err != nil {
return err
}
return nil
}
func (g *conversionGenerator) addImportByPath(pkg string) string {
if name, ok := g.imports[pkg]; ok {
return name
}
name := path.Base(pkg)
if _, ok := g.shortImports[name]; !ok {
g.imports[pkg] = name
g.shortImports[name] = pkg
return name
}
if dirname := path.Base(path.Dir(pkg)); len(dirname) > 0 {
name = dirname + name
if _, ok := g.shortImports[name]; !ok {
g.imports[pkg] = name
g.shortImports[name] = pkg
return name
}
if subdirname := path.Base(path.Dir(path.Dir(pkg))); len(subdirname) > 0 {
name = subdirname + name
if _, ok := g.shortImports[name]; !ok {
g.imports[pkg] = name
g.shortImports[name] = pkg
return name
}
}
}
for i := 2; i < 100; i++ {
generatedName := fmt.Sprintf("%s%d", name, i)
if _, ok := g.shortImports[generatedName]; !ok {
g.imports[pkg] = generatedName
g.shortImports[generatedName] = pkg
return generatedName
}
}
panic(fmt.Sprintf("unable to find a unique name for the package path %q: %v", pkg, g.shortImports))
}
func (g *conversionGenerator) typeName(inType reflect.Type) string {
switch inType.Kind() {
case reflect.Slice:
return fmt.Sprintf("[]%s", g.typeName(inType.Elem()))
case reflect.Ptr:
return fmt.Sprintf("*%s", g.typeName(inType.Elem()))
case reflect.Map:
if len(inType.Name()) == 0 {
return fmt.Sprintf("map[%s]%s", g.typeName(inType.Key()), g.typeName(inType.Elem()))
}
fallthrough
default:
pkg, name := inType.PkgPath(), inType.Name()
if len(name) == 0 && inType.Kind() == reflect.Struct {
return "struct{}"
}
if len(pkg) == 0 {
// Default package.
return name
}
if val, found := g.pkgOverwrites[pkg]; found {
pkg = val
}
if len(pkg) == 0 {
return name
}
short := g.addImportByPath(pkg)
if len(short) > 0 {
return fmt.Sprintf("%s.%s", short, name)
}
return name
}
}
func (g *conversionGenerator) writeDefaultingFunc(b *buffer, inType reflect.Type, indent int) error {
getStmt := "if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {\n"
b.addLine(getStmt, indent)
callFormat := "defaulting.(func(*%s))(in)\n"
callStmt := fmt.Sprintf(callFormat, g.typeName(inType))
b.addLine(callStmt, indent+1)
b.addLine("}\n", indent)
return nil
}
func packageForName(inType reflect.Type) string {
if inType.PkgPath() == "" {
return ""
}
slices := strings.Split(inType.PkgPath(), "/")
return slices[len(slices)-1]
}
func (g *conversionGenerator) conversionFunctionName(inType, outType reflect.Type) string {
funcNameFormat := g.nameFormat
inPkg := packageForName(inType)
outPkg := packageForName(outType)
funcName := fmt.Sprintf(funcNameFormat, inPkg, inType.Name(), outPkg, outType.Name())
return funcName
}
func (g *conversionGenerator) conversionFunctionCall(inType, outType reflect.Type, scopeName string, args ...string) string {
if named, ok := g.publicFuncs[typePair{inType, outType}]; ok {
args[len(args)-1] = scopeName
name := named.name
localPackageName, ok := g.imports[named.packageName]
if !ok {
panic(fmt.Sprintf("have not defined an import for %s", named.packageName))
}
if len(named.packageName) > 0 && len(localPackageName) > 0 {
name = localPackageName + "." + name
}
return fmt.Sprintf("%s(%s)", name, strings.Join(args, ", "))
}
log.Printf("WARNING: Using reflection to convert %v -> %v (no public conversion)", inType, outType)
return fmt.Sprintf("%s.Convert(%s)", scopeName, strings.Join(args, ", "))
}
func (g *conversionGenerator) generatedFunctionName(inType, outType reflect.Type) string {
return g.generatedNamePrefix + g.conversionFunctionName(inType, outType)
}
func (g *conversionGenerator) writeHeader(b *buffer, name, inType, outType string, indent int) {
format := "func %s(in *%s, out *%s, s conversion.Scope) error {\n"
stmt := fmt.Sprintf(format, name, inType, outType)
b.addLine(stmt, indent)
}
func (g *conversionGenerator) writeFooter(b *buffer, indent int) {
b.addLine("return nil\n", indent+1)
b.addLine("}\n", indent)
}
func (g *conversionGenerator) writeConversionForMap(b *buffer, inField, outField reflect.StructField, indent int) error {
ifFormat := "if in.%s != nil {\n"
ifStmt := fmt.Sprintf(ifFormat, inField.Name)
b.addLine(ifStmt, indent)
makeFormat := "out.%s = make(%s)\n"
makeStmt := fmt.Sprintf(makeFormat, outField.Name, g.typeName(outField.Type))
b.addLine(makeStmt, indent+1)
forFormat := "for key, val := range in.%s {\n"
forStmt := fmt.Sprintf(forFormat, inField.Name)
b.addLine(forStmt, indent+1)
// Whether we need to explicitly create a new value.
newValue := false
if isComplexType(inField.Type.Elem()) || !inField.Type.Elem().ConvertibleTo(outField.Type.Elem()) {
newValue = true
newFormat := "newVal := %s{}\n"
newStmt := fmt.Sprintf(newFormat, g.typeName(outField.Type.Elem()))
b.addLine(newStmt, indent+2)
call := g.conversionFunctionCall(inField.Type.Elem(), outField.Type.Elem(), "s", "&val", "&newVal", "0")
convertStmt := fmt.Sprintf("if err := %s; err != nil {\n", call)
b.addLine(convertStmt, indent+2)
b.addLine("return err\n", indent+3)
b.addLine("}\n", indent+2)
}
if inField.Type.Key().ConvertibleTo(outField.Type.Key()) {
value := "val"
if newValue {
value = "newVal"
}
assignStmt := ""
if inField.Type.Key().AssignableTo(outField.Type.Key()) {
assignStmt = fmt.Sprintf("out.%s[key] = %s\n", outField.Name, value)
} else {
assignStmt = fmt.Sprintf("out.%s[%s(key)] = %s\n", outField.Name, g.typeName(outField.Type.Key()), value)
}
b.addLine(assignStmt, indent+2)
} else {
// TODO(wojtek-t): Support maps with keys that are non-convertible to each other.
return fmt.Errorf("conversions between unconvertible keys in map are not supported.")
}
b.addLine("}\n", indent+1)
b.addLine("} else {\n", indent)
nilFormat := "out.%s = nil\n"
nilStmt := fmt.Sprintf(nilFormat, outField.Name)
b.addLine(nilStmt, indent+1)
b.addLine("}\n", indent)
return nil
}
func (g *conversionGenerator) writeConversionForSlice(b *buffer, inField, outField reflect.StructField, indent int) error {
ifFormat := "if in.%s != nil {\n"
ifStmt := fmt.Sprintf(ifFormat, inField.Name)
b.addLine(ifStmt, indent)
makeFormat := "out.%s = make(%s, len(in.%s))\n"
makeStmt := fmt.Sprintf(makeFormat, outField.Name, g.typeName(outField.Type), inField.Name)
b.addLine(makeStmt, indent+1)
forFormat := "for i := range in.%s {\n"
forStmt := fmt.Sprintf(forFormat, inField.Name)
b.addLine(forStmt, indent+1)
assigned := false
switch inField.Type.Elem().Kind() {
case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct:
// Don't copy these via assignment/conversion!
default:
// This should handle all simple types.
if inField.Type.Elem().AssignableTo(outField.Type.Elem()) {
assignFormat := "out.%s[i] = in.%s[i]\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, inField.Name)
b.addLine(assignStmt, indent+2)
assigned = true
} else if inField.Type.Elem().ConvertibleTo(outField.Type.Elem()) {
assignFormat := "out.%s[i] = %s(in.%s[i])\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, g.typeName(outField.Type.Elem()), inField.Name)
b.addLine(assignStmt, indent+2)
assigned = true
}
}
if !assigned {
call := g.conversionFunctionCall(inField.Type.Elem(), outField.Type.Elem(), "s", "&in."+inField.Name+"[i]", "&out."+outField.Name+"[i]", "0")
assignStmt := fmt.Sprintf("if err := %s; err != nil {\n", call)
b.addLine(assignStmt, indent+2)
b.addLine("return err\n", indent+3)
b.addLine("}\n", indent+2)
}
b.addLine("}\n", indent+1)
b.addLine("} else {\n", indent)
nilFormat := "out.%s = nil\n"
nilStmt := fmt.Sprintf(nilFormat, outField.Name)
b.addLine(nilStmt, indent+1)
b.addLine("}\n", indent)
return nil
}
func (g *conversionGenerator) writeConversionForPtr(b *buffer, inField, outField reflect.StructField, indent int) error {
switch inField.Type.Elem().Kind() {
case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct:
// Don't copy these via assignment/conversion!
default:
// This should handle pointers to all simple types.
assignable := inField.Type.Elem().AssignableTo(outField.Type.Elem())
convertible := inField.Type.Elem().ConvertibleTo(outField.Type.Elem())
if assignable || convertible {
ifFormat := "if in.%s != nil {\n"
ifStmt := fmt.Sprintf(ifFormat, inField.Name)
b.addLine(ifStmt, indent)
newFormat := "out.%s = new(%s)\n"
newStmt := fmt.Sprintf(newFormat, outField.Name, g.typeName(outField.Type.Elem()))
b.addLine(newStmt, indent+1)
}
if assignable {
assignFormat := "*out.%s = *in.%s\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, inField.Name)
b.addLine(assignStmt, indent+1)
} else if convertible {
assignFormat := "*out.%s = %s(*in.%s)\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, g.typeName(outField.Type.Elem()), inField.Name)
b.addLine(assignStmt, indent+1)
}
if assignable || convertible {
b.addLine("} else {\n", indent)
nilFormat := "out.%s = nil\n"
nilStmt := fmt.Sprintf(nilFormat, outField.Name)
b.addLine(nilStmt, indent+1)
b.addLine("}\n", indent)
return nil
}
}
b.addLine(fmt.Sprintf("// unable to generate simple pointer conversion for %v -> %v\n", inField.Type.Elem(), outField.Type.Elem()), indent)
ifFormat := "if in.%s != nil {\n"
ifStmt := fmt.Sprintf(ifFormat, inField.Name)
b.addLine(ifStmt, indent)
assignStmt := ""
if _, ok := g.publicFuncs[typePair{inField.Type.Elem(), outField.Type.Elem()}]; ok {
newFormat := "out.%s = new(%s)\n"
newStmt := fmt.Sprintf(newFormat, outField.Name, g.typeName(outField.Type.Elem()))
b.addLine(newStmt, indent+1)
call := g.conversionFunctionCall(inField.Type.Elem(), outField.Type.Elem(), "s", "in."+inField.Name, "out."+outField.Name, "0")
assignStmt = fmt.Sprintf("if err := %s; err != nil {\n", call)
} else {
call := g.conversionFunctionCall(inField.Type.Elem(), outField.Type.Elem(), "s", "&in."+inField.Name, "&out."+outField.Name, "0")
assignStmt = fmt.Sprintf("if err := %s; err != nil {\n", call)
}
b.addLine(assignStmt, indent+1)
b.addLine("return err\n", indent+2)
b.addLine("}\n", indent+1)
b.addLine("} else {\n", indent)
nilFormat := "out.%s = nil\n"
nilStmt := fmt.Sprintf(nilFormat, outField.Name)
b.addLine(nilStmt, indent+1)
b.addLine("}\n", indent)
return nil
}
func (g *conversionGenerator) canTryConversion(b *buffer, inType reflect.Type, inField, outField reflect.StructField, indent int) (bool, error) {
if inField.Type.Kind() != outField.Type.Kind() {
if !g.overridden[inType] {
return false, fmt.Errorf("input %s.%s (%s) does not match output (%s) and conversion is not overridden", inType, inField.Name, inField.Type.Kind(), outField.Type.Kind())
}
b.addLine(fmt.Sprintf("// in.%s has no peer in out\n", inField.Name), indent)
return false, nil
}
return true, nil
}
func (g *conversionGenerator) writeConversionForStruct(b *buffer, inType, outType reflect.Type, indent int) error {
for i := 0; i < inType.NumField(); i++ {
inField := inType.Field(i)
outField, found := outType.FieldByName(inField.Name)
if !found {
if !g.overridden[inType] {
return fmt.Errorf("input %s.%s has no peer in output %s and conversion is not overridden", inType, inField.Name, outType)
}
b.addLine(fmt.Sprintf("// in.%s has no peer in out\n", inField.Name), indent)
continue
}
if g.scheme.Converter().IsConversionIgnored(inField.Type, outField.Type) {
continue
}
existsConversion := g.scheme.Converter().HasConversionFunc(inField.Type, outField.Type)
_, hasPublicConversion := g.publicFuncs[typePair{inField.Type, outField.Type}]
// TODO: This allows a private conversion for a slice to take precedence over a public
// conversion for the field, even though that is technically slower. We should report when
// we generate an inefficient conversion.
if existsConversion || hasPublicConversion {
// Use the conversion method that is already defined.
call := g.conversionFunctionCall(inField.Type, outField.Type, "s", "&in."+inField.Name, "&out."+outField.Name, "0")
assignStmt := fmt.Sprintf("if err := %s; err != nil {\n", call)
b.addLine(assignStmt, indent)
b.addLine("return err\n", indent+1)
b.addLine("}\n", indent)
continue
}
switch inField.Type.Kind() {
case reflect.Map:
if try, err := g.canTryConversion(b, inType, inField, outField, indent); err != nil {
return err
} else if !try {
continue
}
if err := g.writeConversionForMap(b, inField, outField, indent); err != nil {
return err
}
continue
case reflect.Ptr:
if try, err := g.canTryConversion(b, inType, inField, outField, indent); err != nil {
return err
} else if !try {
continue
}
if err := g.writeConversionForPtr(b, inField, outField, indent); err != nil {
return err
}
continue
case reflect.Slice:
if try, err := g.canTryConversion(b, inType, inField, outField, indent); err != nil {
return err
} else if !try {
continue
}
if err := g.writeConversionForSlice(b, inField, outField, indent); err != nil {
return err
}
continue
case reflect.Interface, reflect.Struct:
// Don't copy these via assignment/conversion!
default:
// This should handle all simple types.
if inField.Type.AssignableTo(outField.Type) {
assignFormat := "out.%s = in.%s\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, inField.Name)
b.addLine(assignStmt, indent)
continue
}
if inField.Type.ConvertibleTo(outField.Type) {
assignFormat := "out.%s = %s(in.%s)\n"
assignStmt := fmt.Sprintf(assignFormat, outField.Name, g.typeName(outField.Type), inField.Name)
b.addLine(assignStmt, indent)
continue
}
}
call := g.conversionFunctionCall(inField.Type, outField.Type, "s", "&in."+inField.Name, "&out."+outField.Name, "0")
assignStmt := fmt.Sprintf("if err := %s; err != nil {\n", call)
b.addLine(assignStmt, indent)
b.addLine("return err\n", indent+1)
b.addLine("}\n", indent)
}
return nil
}
func (g *conversionGenerator) writeConversionForType(b *buffer, inType, outType reflect.Type, indent int) error {
// Always emit the auto-generated name.
autoFuncName := g.generatedFunctionName(inType, outType)
g.writeHeader(b, autoFuncName, g.typeName(inType), g.typeName(outType), indent)
if err := g.writeDefaultingFunc(b, inType, indent+1); err != nil {
return err
}
switch inType.Kind() {
case reflect.Struct:
if err := g.writeConversionForStruct(b, inType, outType, indent+1); err != nil {
return err
}
default:
return fmt.Errorf("type not supported: %v", inType)
}
g.writeFooter(b, indent)
b.addLine("\n", 0)
if !g.overridden[inType] {
// Also emit the "user-facing" name.
userFuncName := g.conversionFunctionName(inType, outType)
g.writeHeader(b, userFuncName, g.typeName(inType), g.typeName(outType), indent)
b.addLine(fmt.Sprintf("return %s(in, out, s)\n", autoFuncName), indent+1)
b.addLine("}\n\n", 0)
}
return nil
}
func (g *conversionGenerator) existsConversionFunction(inType, outType reflect.Type) bool {
if val, found := g.convertibles[inType]; found && val == outType {
return true
}
if val, found := g.convertibles[outType]; found && val == inType {
return true
}
return false
}
// TODO(wojtek-t): We should somehow change the conversion methods registered under:
// pkg/runtime/scheme.go to implement the naming convention for conversion functions
// and get rid of this hack.
type typePair struct {
inType reflect.Type
outType reflect.Type
}
var defaultConversions []typePair = []typePair{}
func (g *conversionGenerator) OverwritePackage(pkg, overwrite string) {
g.pkgOverwrites[pkg] = overwrite
}

View File

@ -1,76 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package runtime
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
)
type InternalSubtype struct {
String string
}
type Internal struct {
TypeMeta
Bool bool
Complex InternalSubtype
}
type ExternalSubtype struct {
String string
}
type External struct {
TypeMeta
Complex ExternalSubtype
}
func (obj *Internal) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *External) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func TestGenerateConversionsForStruct(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: APIVersionInternal}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "external"}
scheme := NewScheme()
scheme.Log(t)
scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &Internal{})
scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &External{})
generator := NewConversionGenerator(scheme, "foo")
typedGenerator, ok := generator.(*conversionGenerator)
if !ok {
t.Fatalf("error converting to conversionGenerator")
}
internalType := reflect.TypeOf(Internal{})
externalType := reflect.TypeOf(External{})
err := typedGenerator.generateConversionsForStruct(internalType, externalType)
if err == nil {
t.Errorf("expected error for asymmetrical field")
}
// we are expecting Convert_runtime_InternalSubtype_To_runtime_ExternalSubtype to be generated
// even though the conversion for the parent type cannot be auto generated
if len(typedGenerator.publicFuncs) != 1 {
t.Errorf("expected to find one public conversion for the Complex type but found: %v", typedGenerator.publicFuncs)
}
}

View File

@ -113,6 +113,37 @@ func fieldName(field *ast.Field) string {
return jsonTag
}
// A buffer of lines that will be written.
type bufferedLine struct {
line string
indentation int
}
type buffer struct {
lines []bufferedLine
}
func newBuffer() *buffer {
return &buffer{
lines: make([]bufferedLine, 0),
}
}
func (b *buffer) addLine(line string, indent int) {
b.lines = append(b.lines, bufferedLine{line, indent})
}
func (b *buffer) flushLines(w io.Writer) error {
for _, line := range b.lines {
indentation := strings.Repeat("\t", line.indentation)
fullLine := fmt.Sprintf("%s%s", indentation, line.line)
if _, err := io.WriteString(w, fullLine); err != nil {
return err
}
}
return nil
}
func writeFuncHeader(b *buffer, structName string, indent int) {
s := fmt.Sprintf("var map_%s = map[string]string {\n", structName)
b.addLine(s, indent)