4c060a78cc
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
349 lines
11 KiB
Go
349 lines
11 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
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.
|
|
*/
|
|
|
|
// go-to-protobuf generates a Protobuf IDL from a Go struct, respecting any
|
|
// existing IDL tags on the Go struct.
|
|
package protobuf
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"k8s.io/code-generator/pkg/util"
|
|
"k8s.io/gengo/args"
|
|
"k8s.io/gengo/generator"
|
|
"k8s.io/gengo/namer"
|
|
"k8s.io/gengo/parser"
|
|
"k8s.io/gengo/types"
|
|
|
|
flag "github.com/spf13/pflag"
|
|
)
|
|
|
|
type Generator struct {
|
|
Common args.GeneratorArgs
|
|
APIMachineryPackages string
|
|
Packages string
|
|
OutputBase string
|
|
VendorOutputBase string
|
|
ProtoImport []string
|
|
Conditional string
|
|
Clean bool
|
|
OnlyIDL bool
|
|
KeepGogoproto bool
|
|
SkipGeneratedRewrite bool
|
|
DropEmbeddedFields string
|
|
}
|
|
|
|
func New() *Generator {
|
|
sourceTree := args.DefaultSourceTree()
|
|
common := args.GeneratorArgs{
|
|
OutputBase: sourceTree,
|
|
GoHeaderFilePath: filepath.Join(sourceTree, util.BoilerplatePath()),
|
|
}
|
|
defaultProtoImport := filepath.Join(sourceTree, "k8s.io", "kubernetes", "vendor", "github.com", "gogo", "protobuf", "protobuf")
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatalf("Cannot get current directory.")
|
|
}
|
|
return &Generator{
|
|
Common: common,
|
|
OutputBase: sourceTree,
|
|
VendorOutputBase: filepath.Join(cwd, "vendor"),
|
|
ProtoImport: []string{defaultProtoImport},
|
|
APIMachineryPackages: strings.Join([]string{
|
|
`+k8s.io/apimachinery/pkg/util/intstr`,
|
|
`+k8s.io/apimachinery/pkg/api/resource`,
|
|
`+k8s.io/apimachinery/pkg/runtime/schema`,
|
|
`+k8s.io/apimachinery/pkg/runtime`,
|
|
`k8s.io/apimachinery/pkg/apis/meta/v1`,
|
|
`k8s.io/apimachinery/pkg/apis/meta/v1beta1`,
|
|
`k8s.io/apimachinery/pkg/apis/testapigroup/v1`,
|
|
}, ","),
|
|
Packages: "",
|
|
DropEmbeddedFields: "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta",
|
|
}
|
|
}
|
|
|
|
func (g *Generator) BindFlags(flag *flag.FlagSet) {
|
|
flag.StringVarP(&g.Common.GoHeaderFilePath, "go-header-file", "h", g.Common.GoHeaderFilePath, "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.")
|
|
flag.BoolVar(&g.Common.VerifyOnly, "verify-only", g.Common.VerifyOnly, "If true, only verify existing output, do not write anything.")
|
|
flag.StringVarP(&g.Packages, "packages", "p", g.Packages, "comma-separated list of directories to get input types from. Directories prefixed with '-' are not generated, directories prefixed with '+' only create types with explicit IDL instructions.")
|
|
flag.StringVar(&g.APIMachineryPackages, "apimachinery-packages", g.APIMachineryPackages, "comma-separated list of directories to get apimachinery input types from which are needed by any API. Directories prefixed with '-' are not generated, directories prefixed with '+' only create types with explicit IDL instructions.")
|
|
flag.StringVarP(&g.OutputBase, "output-base", "o", g.OutputBase, "Output base; defaults to $GOPATH/src/")
|
|
flag.StringVar(&g.VendorOutputBase, "vendor-output-base", g.VendorOutputBase, "The vendor/ directory to look for packages in; defaults to $PWD/vendor/.")
|
|
flag.StringSliceVar(&g.ProtoImport, "proto-import", g.ProtoImport, "The search path for the core protobuf .protos, required; defaults $GOPATH/src/k8s.io/kubernetes/vendor/github.com/gogo/protobuf/protobuf.")
|
|
flag.StringVar(&g.Conditional, "conditional", g.Conditional, "An optional Golang build tag condition to add to the generated Go code")
|
|
flag.BoolVar(&g.Clean, "clean", g.Clean, "If true, remove all generated files for the specified Packages.")
|
|
flag.BoolVar(&g.OnlyIDL, "only-idl", g.OnlyIDL, "If true, only generate the IDL for each package.")
|
|
flag.BoolVar(&g.KeepGogoproto, "keep-gogoproto", g.KeepGogoproto, "If true, the generated IDL will contain gogoprotobuf extensions which are normally removed")
|
|
flag.BoolVar(&g.SkipGeneratedRewrite, "skip-generated-rewrite", g.SkipGeneratedRewrite, "If true, skip fixing up the generated.pb.go file (debugging only).")
|
|
flag.StringVar(&g.DropEmbeddedFields, "drop-embedded-fields", g.DropEmbeddedFields, "Comma-delimited list of embedded Go types to omit from generated protobufs")
|
|
}
|
|
|
|
func Run(g *Generator) {
|
|
if g.Common.VerifyOnly {
|
|
g.OnlyIDL = true
|
|
g.Clean = false
|
|
}
|
|
|
|
b := parser.New()
|
|
b.AddBuildTags("proto")
|
|
|
|
omitTypes := map[types.Name]struct{}{}
|
|
for _, t := range strings.Split(g.DropEmbeddedFields, ",") {
|
|
name := types.Name{}
|
|
if i := strings.LastIndex(t, "."); i != -1 {
|
|
name.Package, name.Name = t[:i], t[i+1:]
|
|
} else {
|
|
name.Name = t
|
|
}
|
|
if len(name.Name) == 0 {
|
|
log.Fatalf("--drop-embedded-types requires names in the form of [GOPACKAGE.]TYPENAME: %v", t)
|
|
}
|
|
omitTypes[name] = struct{}{}
|
|
}
|
|
|
|
boilerplate, err := g.Common.LoadGoBoilerplate()
|
|
if err != nil {
|
|
log.Fatalf("Failed loading boilerplate (consider using the go-header-file flag): %v", err)
|
|
}
|
|
|
|
protobufNames := NewProtobufNamer()
|
|
outputPackages := generator.Packages{}
|
|
nonOutputPackages := map[string]struct{}{}
|
|
|
|
var packages []string
|
|
if len(g.APIMachineryPackages) != 0 {
|
|
packages = append(packages, strings.Split(g.APIMachineryPackages, ",")...)
|
|
}
|
|
if len(g.Packages) != 0 {
|
|
packages = append(packages, strings.Split(g.Packages, ",")...)
|
|
}
|
|
if len(packages) == 0 {
|
|
log.Fatalf("Both apimachinery-packages and packages are empty. At least one package must be specified.")
|
|
}
|
|
|
|
for _, d := range packages {
|
|
generateAllTypes, outputPackage := true, true
|
|
switch {
|
|
case strings.HasPrefix(d, "+"):
|
|
d = d[1:]
|
|
generateAllTypes = false
|
|
case strings.HasPrefix(d, "-"):
|
|
d = d[1:]
|
|
outputPackage = false
|
|
}
|
|
name := protoSafePackage(d)
|
|
parts := strings.SplitN(d, "=", 2)
|
|
if len(parts) > 1 {
|
|
d = parts[0]
|
|
name = parts[1]
|
|
}
|
|
p := newProtobufPackage(d, name, generateAllTypes, omitTypes)
|
|
header := append([]byte{}, boilerplate...)
|
|
header = append(header, p.HeaderText...)
|
|
p.HeaderText = header
|
|
protobufNames.Add(p)
|
|
if outputPackage {
|
|
outputPackages = append(outputPackages, p)
|
|
} else {
|
|
nonOutputPackages[name] = struct{}{}
|
|
}
|
|
}
|
|
|
|
if !g.Common.VerifyOnly {
|
|
for _, p := range outputPackages {
|
|
if err := p.(*protobufPackage).Clean(g.OutputBase); err != nil {
|
|
log.Fatalf("Unable to clean package %s: %v", p.Name(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if g.Clean {
|
|
return
|
|
}
|
|
|
|
for _, p := range protobufNames.List() {
|
|
if err := b.AddDir(p.Path()); err != nil {
|
|
log.Fatalf("Unable to add directory %q: %v", p.Path(), err)
|
|
}
|
|
}
|
|
|
|
c, err := generator.NewContext(
|
|
b,
|
|
namer.NameSystems{
|
|
"public": namer.NewPublicNamer(3),
|
|
"proto": protobufNames,
|
|
},
|
|
"public",
|
|
)
|
|
if err != nil {
|
|
log.Fatalf("Failed making a context: %v", err)
|
|
}
|
|
|
|
c.Verify = g.Common.VerifyOnly
|
|
c.FileTypes["protoidl"] = NewProtoFile()
|
|
|
|
var vendoredOutputPackages, localOutputPackages generator.Packages
|
|
for _, p := range protobufNames.packages {
|
|
if _, ok := nonOutputPackages[p.Name()]; ok {
|
|
// if we're not outputting the package, don't include it in either package list
|
|
continue
|
|
}
|
|
p.Vendored = strings.Contains(c.Universe[p.PackagePath].SourcePath, "/vendor/")
|
|
if p.Vendored {
|
|
vendoredOutputPackages = append(vendoredOutputPackages, p)
|
|
} else {
|
|
localOutputPackages = append(localOutputPackages, p)
|
|
}
|
|
}
|
|
|
|
if err := protobufNames.AssignTypesToPackages(c); err != nil {
|
|
log.Fatalf("Failed to identify Common types: %v", err)
|
|
}
|
|
|
|
if err := c.ExecutePackages(g.VendorOutputBase, vendoredOutputPackages); err != nil {
|
|
log.Fatalf("Failed executing vendor generator: %v", err)
|
|
}
|
|
if err := c.ExecutePackages(g.OutputBase, localOutputPackages); err != nil {
|
|
log.Fatalf("Failed executing local generator: %v", err)
|
|
}
|
|
|
|
if g.OnlyIDL {
|
|
return
|
|
}
|
|
|
|
if _, err := exec.LookPath("protoc"); err != nil {
|
|
log.Fatalf("Unable to find 'protoc': %v", err)
|
|
}
|
|
|
|
searchArgs := []string{"-I", ".", "-I", g.OutputBase}
|
|
if len(g.ProtoImport) != 0 {
|
|
for _, s := range g.ProtoImport {
|
|
searchArgs = append(searchArgs, "-I", s)
|
|
}
|
|
}
|
|
args := append(searchArgs, fmt.Sprintf("--gogo_out=%s", g.OutputBase))
|
|
|
|
buf := &bytes.Buffer{}
|
|
if len(g.Conditional) > 0 {
|
|
fmt.Fprintf(buf, "// +build %s\n\n", g.Conditional)
|
|
}
|
|
buf.Write(boilerplate)
|
|
|
|
for _, outputPackage := range outputPackages {
|
|
p := outputPackage.(*protobufPackage)
|
|
|
|
path := filepath.Join(g.OutputBase, p.ImportPath())
|
|
outputPath := filepath.Join(g.OutputBase, p.OutputPath())
|
|
if p.Vendored {
|
|
path = filepath.Join(g.VendorOutputBase, p.ImportPath())
|
|
outputPath = filepath.Join(g.VendorOutputBase, p.OutputPath())
|
|
}
|
|
|
|
// generate the gogoprotobuf protoc
|
|
cmd := exec.Command("protoc", append(args, path)...)
|
|
out, err := cmd.CombinedOutput()
|
|
if len(out) > 0 {
|
|
log.Printf(string(out))
|
|
}
|
|
if err != nil {
|
|
log.Println(strings.Join(cmd.Args, " "))
|
|
log.Fatalf("Unable to generate protoc on %s: %v", p.PackageName, err)
|
|
}
|
|
|
|
if g.SkipGeneratedRewrite {
|
|
continue
|
|
}
|
|
|
|
// alter the generated protobuf file to remove the generated types (but leave the serializers) and rewrite the
|
|
// package statement to match the desired package name
|
|
if err := RewriteGeneratedGogoProtobufFile(outputPath, p.ExtractGeneratedType, p.OptionalTypeName, buf.Bytes()); err != nil {
|
|
log.Fatalf("Unable to rewrite generated %s: %v", outputPath, err)
|
|
}
|
|
|
|
// sort imports
|
|
cmd = exec.Command("goimports", "-w", outputPath)
|
|
out, err = cmd.CombinedOutput()
|
|
if len(out) > 0 {
|
|
log.Printf(string(out))
|
|
}
|
|
if err != nil {
|
|
log.Println(strings.Join(cmd.Args, " "))
|
|
log.Fatalf("Unable to rewrite imports for %s: %v", p.PackageName, err)
|
|
}
|
|
|
|
// format and simplify the generated file
|
|
cmd = exec.Command("gofmt", "-s", "-w", outputPath)
|
|
out, err = cmd.CombinedOutput()
|
|
if len(out) > 0 {
|
|
log.Printf(string(out))
|
|
}
|
|
if err != nil {
|
|
log.Println(strings.Join(cmd.Args, " "))
|
|
log.Fatalf("Unable to apply gofmt for %s: %v", p.PackageName, err)
|
|
}
|
|
}
|
|
|
|
if g.SkipGeneratedRewrite {
|
|
return
|
|
}
|
|
|
|
if !g.KeepGogoproto {
|
|
// generate, but do so without gogoprotobuf extensions
|
|
for _, outputPackage := range outputPackages {
|
|
p := outputPackage.(*protobufPackage)
|
|
p.OmitGogo = true
|
|
}
|
|
if err := c.ExecutePackages(g.VendorOutputBase, vendoredOutputPackages); err != nil {
|
|
log.Fatalf("Failed executing vendor generator: %v", err)
|
|
}
|
|
if err := c.ExecutePackages(g.OutputBase, localOutputPackages); err != nil {
|
|
log.Fatalf("Failed executing local generator: %v", err)
|
|
}
|
|
}
|
|
|
|
for _, outputPackage := range outputPackages {
|
|
p := outputPackage.(*protobufPackage)
|
|
|
|
if len(p.StructTags) == 0 {
|
|
continue
|
|
}
|
|
|
|
pattern := filepath.Join(g.OutputBase, p.PackagePath, "*.go")
|
|
if p.Vendored {
|
|
pattern = filepath.Join(g.VendorOutputBase, p.PackagePath, "*.go")
|
|
}
|
|
files, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
log.Fatalf("Can't glob pattern %q: %v", pattern, err)
|
|
}
|
|
|
|
for _, s := range files {
|
|
if strings.HasSuffix(s, "_test.go") {
|
|
continue
|
|
}
|
|
if err := RewriteTypesWithProtobufStructTags(s, p.StructTags); err != nil {
|
|
log.Fatalf("Unable to rewrite with struct tags %s: %v", s, err)
|
|
}
|
|
}
|
|
}
|
|
}
|