mirror of https://github.com/k3s-io/k3s
Remove old conversion generator
parent
490c68dbe3
commit
89e860e622
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -51,7 +51,6 @@ if ! $ALL ; then
|
|||
fi
|
||||
|
||||
BASH_TARGETS="codecgen
|
||||
generated-conversions
|
||||
generated-docs
|
||||
generated-swagger-docs
|
||||
swagger-spec
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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!"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue