aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorOndrej Fabry <ofabry@cisco.com>2019-06-26 16:28:20 +0200
committerOndrej Fabry <ofabry@cisco.com>2019-06-27 07:33:14 +0200
commitef471318d66dd2832df4dc929d312f7cd5f7009a (patch)
tree49dda363eaa7ac3102425aba9f13a503b2a04f48 /cmd
parentda15c397b3dbbba07d159b3af767aa13d443cfd6 (diff)
Improvements for binapi-generator and support VPP 19.04 in statsclient
- RPC service client implementation for dumps requests now streams responses - RPC service generation is now enabled by default - examples now allow setting binapi socket address - input dir flag for binapi-generator will recursively look into dirs to support core/plugins in /usr/share/vpp/api - minor improvements in debug logs - add support for VPP 19.04 for statsclient Change-Id: I0939ee3aa6e9f850d073fc5c87aff4ccc56b0d70 Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/binapi-generator/generate.go290
-rw-r--r--cmd/binapi-generator/generate_test.go6
-rw-r--r--cmd/binapi-generator/main.go97
-rw-r--r--cmd/binapi-generator/objects.go26
-rw-r--r--cmd/binapi-generator/parse.go16
5 files changed, 223 insertions, 212 deletions
diff --git a/cmd/binapi-generator/generate.go b/cmd/binapi-generator/generate.go
index 1ff3e4c..e386f8d 100644
--- a/cmd/binapi-generator/generate.go
+++ b/cmd/binapi-generator/generate.go
@@ -41,6 +41,10 @@ const (
constVersionCrc = "VersionCrc" // version CRC constant
unionDataField = "XXX_UnionData" // name for the union data field
+
+ serviceApiName = "RPCService" // name for the RPC service interface
+ serviceImplName = "serviceClient" // name for the RPC service implementation
+ serviceClientName = "ServiceClient" // name for the RPC service client
)
// context is a structure storing data for code generation
@@ -61,8 +65,8 @@ type context struct {
packageData *Package // parsed package data
}
-// getContext returns context details of the code generation task
-func getContext(inputFile, outputDir string) (*context, error) {
+// newContext returns context details of the code generation task
+func newContext(inputFile, outputDir string) (*context, error) {
if !strings.HasSuffix(inputFile, inputFileExt) {
return nil, fmt.Errorf("invalid input file name: %q", inputFile)
}
@@ -93,13 +97,14 @@ func getContext(inputFile, outputDir string) (*context, error) {
return ctx, nil
}
-// generatePackage generates code for the parsed package data and writes it into w
func generatePackage(ctx *context, w io.Writer) error {
logf("generating package %q", ctx.packageName)
- // generate file header
+ fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
+ fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
+ fmt.Fprintln(w)
+
generateHeader(ctx, w)
- generateImports(ctx, w)
// generate module desc
fmt.Fprintln(w, "const (")
@@ -119,8 +124,6 @@ func generatePackage(ctx *context, w io.Writer) error {
// generate enums
if len(ctx.packageData.Enums) > 0 {
- fmt.Fprintf(w, "/* Enums */\n\n")
-
for _, enum := range ctx.packageData.Enums {
generateEnum(ctx, w, &enum)
}
@@ -128,8 +131,6 @@ func generatePackage(ctx *context, w io.Writer) error {
// generate aliases
if len(ctx.packageData.Aliases) > 0 {
- fmt.Fprintf(w, "/* Aliases */\n\n")
-
for _, alias := range ctx.packageData.Aliases {
generateAlias(ctx, w, &alias)
}
@@ -137,8 +138,6 @@ func generatePackage(ctx *context, w io.Writer) error {
// generate types
if len(ctx.packageData.Types) > 0 {
- fmt.Fprintf(w, "/* Types */\n\n")
-
for _, typ := range ctx.packageData.Types {
generateType(ctx, w, &typ)
}
@@ -146,8 +145,6 @@ func generatePackage(ctx *context, w io.Writer) error {
// generate unions
if len(ctx.packageData.Unions) > 0 {
- fmt.Fprintf(w, "/* Unions */\n\n")
-
for _, union := range ctx.packageData.Unions {
generateUnion(ctx, w, &union)
}
@@ -155,8 +152,6 @@ func generatePackage(ctx *context, w io.Writer) error {
// generate messages
if len(ctx.packageData.Messages) > 0 {
- fmt.Fprintf(w, "/* Messages */\n\n")
-
for _, msg := range ctx.packageData.Messages {
generateMessage(ctx, w, &msg)
}
@@ -189,20 +184,17 @@ func generatePackage(ctx *context, w io.Writer) error {
}
}
+ generateFooter(ctx, w)
+
return nil
}
-// generateHeader writes generated package header into w
func generateHeader(ctx *context, w io.Writer) {
- fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
- fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
- fmt.Fprintln(w)
-
fmt.Fprintln(w, "/*")
- fmt.Fprintf(w, "Package %s is a generated from VPP binary API module '%s'.\n", ctx.packageName, ctx.moduleName)
+ fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
fmt.Fprintln(w)
- fmt.Fprintf(w, " The %s module consists of:\n", ctx.moduleName)
- var printObjNum = func(obj string, num int) {
+ fmt.Fprintln(w, "It consists of:")
+ printObjNum := func(obj string, num int) {
if num > 0 {
if num > 1 {
if strings.HasSuffix(obj, "s") {
@@ -215,7 +207,6 @@ func generateHeader(ctx *context, w io.Writer) {
fmt.Fprintf(w, "\t%3d %s\n", num, obj)
}
}
-
printObjNum("enum", len(ctx.packageData.Enums))
printObjNum("alias", len(ctx.packageData.Aliases))
printObjNum("type", len(ctx.packageData.Types))
@@ -223,42 +214,42 @@ func generateHeader(ctx *context, w io.Writer) {
printObjNum("message", len(ctx.packageData.Messages))
printObjNum("service", len(ctx.packageData.Services))
fmt.Fprintln(w, "*/")
-
fmt.Fprintf(w, "package %s\n", ctx.packageName)
fmt.Fprintln(w)
+
+ fmt.Fprintln(w, "import (")
+ fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
+ fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
+ fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
+ fmt.Fprintf(w, "\tio \"%s\"\n", "io")
+ fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
+ fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
+ fmt.Fprintln(w, ")")
+ fmt.Fprintln(w)
}
-// generateImports writes generated package imports into w
-func generateImports(ctx *context, w io.Writer) {
- fmt.Fprintf(w, "import api \"%s\"\n", govppApiImportPath)
- fmt.Fprintf(w, "import bytes \"%s\"\n", "bytes")
- fmt.Fprintf(w, "import context \"%s\"\n", "context")
- fmt.Fprintf(w, "import strconv \"%s\"\n", "strconv")
- fmt.Fprintf(w, "import struc \"%s\"\n", "github.com/lunixbochs/struc")
+func generateFooter(ctx *context, w io.Writer) {
+ fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
+ fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
+ fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
+ fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
+ fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
fmt.Fprintln(w)
fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
fmt.Fprintf(w, "var _ = context.Background\n")
+ fmt.Fprintf(w, "var _ = io.Copy\n")
fmt.Fprintf(w, "var _ = strconv.Itoa\n")
fmt.Fprintf(w, "var _ = struc.Pack\n")
- fmt.Fprintln(w)
-
- fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
- fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
- fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
- fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
- fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
- fmt.Fprintln(w)
}
-// generateComment writes generated comment for the object into w
func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
if objKind == "service" {
- fmt.Fprintf(w, "// %s represents VPP binary API services in %s module.\n", goName, ctx.moduleName)
+ fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
} else {
- fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName)
+ fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
}
if !ctx.includeComments {
@@ -315,97 +306,6 @@ func generateComment(ctx *context, w io.Writer, goName string, vppName string, o
fmt.Fprintln(w, "//")
}
-// generateServices writes generated code for the Services interface into w
-func generateServices(ctx *context, w io.Writer, services []Service) {
- const apiName = "Service"
- const implName = "service"
-
- // generate services comment
- generateComment(ctx, w, apiName, "services", "service")
-
- // generate interface
- fmt.Fprintf(w, "type %s interface {\n", apiName)
- for _, svc := range services {
- generateServiceMethod(ctx, w, &svc)
- fmt.Fprintln(w)
- }
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- // generate client implementation
- fmt.Fprintf(w, "type %s struct {\n", implName)
- fmt.Fprintf(w, "\tch api.Channel\n")
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- fmt.Fprintf(w, "func New%[1]s(ch api.Channel) %[1]s {\n", apiName)
- fmt.Fprintf(w, "\treturn &%s{ch}\n", implName)
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
-
- for _, svc := range services {
- fmt.Fprintf(w, "func (c *%s) ", implName)
- generateServiceMethod(ctx, w, &svc)
- fmt.Fprintln(w, " {")
- if svc.Stream {
- // TODO: stream responses
- //fmt.Fprintf(w, "\tstream := make(chan *%s)\n", camelCaseName(svc.ReplyType))
- replyTyp := camelCaseName(svc.ReplyType)
- fmt.Fprintf(w, "\tvar dump []*%s\n", replyTyp)
- fmt.Fprintf(w, "\treq := c.ch.SendMultiRequest(in)\n")
- fmt.Fprintf(w, "\tfor {\n")
- fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
- fmt.Fprintf(w, "\tstop, err := req.ReceiveReply(m)\n")
- fmt.Fprintf(w, "\tif stop { break }\n")
- fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
- fmt.Fprintf(w, "\tdump = append(dump, m)\n")
- fmt.Fprintln(w, "}")
- fmt.Fprintf(w, "\treturn dump, nil\n")
- } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
- fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
- fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
- fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
- fmt.Fprintf(w, "\treturn out, nil\n")
- } else {
- fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
- fmt.Fprintf(w, "\treturn nil\n")
- }
- fmt.Fprintln(w, "}")
- fmt.Fprintln(w)
- }
-
- fmt.Fprintln(w)
-}
-
-// generateServiceMethod writes generated code for the service into w
-func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
- reqTyp := camelCaseName(svc.RequestType)
-
- // method name is same as parameter type name by default
- method := reqTyp
- if svc.Stream {
- // use Dump as prefix instead of suffix for stream services
- if m := strings.TrimSuffix(method, "Dump"); method != m {
- method = "Dump" + m
- }
- }
-
- params := fmt.Sprintf("in *%s", reqTyp)
- returns := "error"
- if replyType := camelCaseName(svc.ReplyType); replyType != "" {
- replyTyp := fmt.Sprintf("*%s", replyType)
- if svc.Stream {
- // TODO: stream responses
- //replyTyp = fmt.Sprintf("<-chan %s", replyTyp)
- replyTyp = fmt.Sprintf("[]%s", replyTyp)
- }
- returns = fmt.Sprintf("(%s, error)", replyTyp)
- }
-
- fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
-}
-
-// generateEnum writes generated code for the enum into w
func generateEnum(ctx *context, w io.Writer, enum *Enum) {
name := camelCaseName(enum.Name)
typ := binapiTypes[enum.Type]
@@ -450,7 +350,6 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) {
fmt.Fprintln(w)
}
-// generateAlias writes generated code for the alias into w
func generateAlias(ctx *context, w io.Writer, alias *Alias) {
name := camelCaseName(alias.Name)
@@ -472,7 +371,6 @@ func generateAlias(ctx *context, w io.Writer, alias *Alias) {
fmt.Fprintln(w)
}
-// generateUnion writes generated code for the union into w
func generateUnion(ctx *context, w io.Writer, union *Union) {
name := camelCaseName(union.Name)
@@ -561,7 +459,6 @@ func (u *%[1]s) Get%[2]s() (a %[3]s) {
`, structName, getterField, getterStruct, unionDataField)
}
-// generateType writes generated code for the type into w
func generateType(ctx *context, w io.Writer, typ *Type) {
name := camelCaseName(typ.Name)
@@ -598,7 +495,6 @@ func generateType(ctx *context, w io.Writer, typ *Type) {
fmt.Fprintln(w)
}
-// generateMessage writes generated code for the message into w
func generateMessage(ctx *context, w io.Writer, msg *Message) {
name := camelCaseName(msg.Name)
@@ -666,7 +562,6 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
fmt.Fprintln(w)
}
-// generateField writes generated code for the field into w
func generateField(ctx *context, w io.Writer, fields []Field, i int) {
field := fields[i]
@@ -737,7 +632,6 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) {
fmt.Fprintln(w)
}
-// generateMessageNameGetter generates getter for original VPP message name into the provider writer
func generateMessageNameGetter(w io.Writer, structName, msgName string) {
fmt.Fprintf(w, `func (*%s) GetMessageName() string {
return %q
@@ -745,7 +639,6 @@ func generateMessageNameGetter(w io.Writer, structName, msgName string) {
`, structName, msgName)
}
-// generateTypeNameGetter generates getter for original VPP type name into the provider writer
func generateTypeNameGetter(w io.Writer, structName, msgName string) {
fmt.Fprintf(w, `func (*%s) GetTypeName() string {
return %q
@@ -753,7 +646,6 @@ func generateTypeNameGetter(w io.Writer, structName, msgName string) {
`, structName, msgName)
}
-// generateCrcGetter generates getter for CRC checksum of the message definition into the provider writer
func generateCrcGetter(w io.Writer, structName, crc string) {
crc = strings.TrimPrefix(crc, "0x")
fmt.Fprintf(w, `func (*%s) GetCrcString() string {
@@ -762,7 +654,6 @@ func generateCrcGetter(w io.Writer, structName, crc string) {
`, structName, crc)
}
-// generateMessageTypeGetter generates message factory for the generated message into the provider writer
func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
if msgType == requestMessage {
@@ -776,3 +667,116 @@ func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageTy
}
fmt.Fprintln(w, "}")
}
+
+func generateServices(ctx *context, w io.Writer, services []Service) {
+
+ // generate services comment
+ generateComment(ctx, w, serviceApiName, "services", "service")
+
+ // generate service api
+ fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
+ for _, svc := range services {
+ generateServiceMethod(ctx, w, &svc)
+ fmt.Fprintln(w)
+ }
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ // generate client implementation
+ fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
+ fmt.Fprintf(w, "\tch api.Channel\n")
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ // generate client constructor
+ fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
+ fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ for _, svc := range services {
+ method := camelCaseName(svc.RequestType)
+ if m := strings.TrimSuffix(method, "Dump"); method != m {
+ method = "Dump" + m
+ }
+
+ fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
+ generateServiceMethod(ctx, w, &svc)
+ fmt.Fprintln(w, " {")
+ if svc.Stream {
+ streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
+ fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
+ fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
+ fmt.Fprintf(w, "\treturn x, nil\n")
+ } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
+ fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
+ fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
+ fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
+ fmt.Fprintf(w, "\treturn out, nil\n")
+ } else {
+ fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
+ fmt.Fprintf(w, "\treturn nil\n")
+ }
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ if svc.Stream {
+ replyTyp := camelCaseName(svc.ReplyType)
+ method := camelCaseName(svc.RequestType)
+ if m := strings.TrimSuffix(method, "Dump"); method != m {
+ method = "Dump" + m
+ }
+ streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
+
+ fmt.Fprintf(w, "type %s interface {\n", streamApi)
+ fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
+ fmt.Fprintf(w, "type %s struct {\n", streamImpl)
+ fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+
+ fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
+ fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
+ fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
+ fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
+ fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
+ fmt.Fprintf(w, "\treturn m, nil\n")
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+ }
+ }
+
+ fmt.Fprintln(w)
+}
+
+func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
+ reqTyp := camelCaseName(svc.RequestType)
+
+ // method name is same as parameter type name by default
+ method := reqTyp
+ if svc.Stream {
+ // use Dump as prefix instead of suffix for stream services
+ if m := strings.TrimSuffix(method, "Dump"); method != m {
+ method = "Dump" + m
+ }
+ }
+
+ params := fmt.Sprintf("in *%s", reqTyp)
+ returns := "error"
+
+ if replyType := camelCaseName(svc.ReplyType); replyType != "" {
+ var replyTyp string
+ if svc.Stream {
+ replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
+ } else {
+ replyTyp = fmt.Sprintf("*%s", replyType)
+ }
+ returns = fmt.Sprintf("(%s, error)", replyTyp)
+ }
+
+ fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
+}
diff --git a/cmd/binapi-generator/generate_test.go b/cmd/binapi-generator/generate_test.go
index bac5b51..e68b54a 100644
--- a/cmd/binapi-generator/generate_test.go
+++ b/cmd/binapi-generator/generate_test.go
@@ -85,7 +85,7 @@ func TestGenerateFromFileGeneratePackageError(t *testing.T) {
func TestGetContext(t *testing.T) {
RegisterTestingT(t)
outDir := "test_output_directory"
- result, err := getContext("testdata/af_packet.api.json", outDir)
+ result, err := newContext("testdata/af_packet.api.json", outDir)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).ToNot(BeNil())
Expect(result.outputFile).To(BeEquivalentTo(outDir + "/af_packet/af_packet.ba.go"))
@@ -94,7 +94,7 @@ func TestGetContext(t *testing.T) {
func TestGetContextNoJsonFile(t *testing.T) {
RegisterTestingT(t)
outDir := "test_output_directory"
- result, err := getContext("testdata/input.txt", outDir)
+ result, err := newContext("testdata/input.txt", outDir)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid input file name"))
Expect(result).To(BeNil())
@@ -103,7 +103,7 @@ func TestGetContextNoJsonFile(t *testing.T) {
func TestGetContextInterfaceJson(t *testing.T) {
RegisterTestingT(t)
outDir := "test_output_directory"
- result, err := getContext("testdata/ip.api.json", outDir)
+ result, err := newContext("testdata/ip.api.json", outDir)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).ToNot(BeNil())
Expect(result.outputFile)
diff --git a/cmd/binapi-generator/main.go b/cmd/binapi-generator/main.go
index d221317..89a2b2d 100644
--- a/cmd/binapi-generator/main.go
+++ b/cmd/binapi-generator/main.go
@@ -30,15 +30,18 @@ import (
)
var (
- inputFile = flag.String("input-file", "", "Input file with VPP API in JSON format.")
- inputDir = flag.String("input-dir", ".", "Input directory with VPP API files in JSON format.")
- outputDir = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
+ theInputFile = flag.String("input-file", "", "Input file with VPP API in JSON format.")
+ theInputDir = flag.String("input-dir", "/usr/share/vpp/api", "Input directory with VPP API files in JSON format.")
+ theOutputDir = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
+
includeAPIVer = flag.Bool("include-apiver", true, "Include APIVersion constant for each module.")
+ includeServices = flag.Bool("include-services", true, "Include RPC service api and client implementation.")
includeComments = flag.Bool("include-comments", false, "Include JSON API source in comments for each object.")
includeBinapiNames = flag.Bool("include-binapi-names", false, "Include binary API names in struct tag.")
- includeServices = flag.Bool("include-services", false, "Include service interface with client implementation.")
- continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
- debug = flag.Bool("debug", debugMode, "Enable debug mode.")
+
+ continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
+
+ debug = flag.Bool("debug", debugMode, "Enable debug mode.")
)
var debugMode = os.Getenv("DEBUG_BINAPI_GENERATOR") != ""
@@ -49,69 +52,84 @@ func main() {
logrus.SetLevel(logrus.DebugLevel)
}
- if *inputFile == "" && *inputDir == "" {
- fmt.Fprintln(os.Stderr, "ERROR: input-file or input-dir must be specified")
+ if err := run(*theInputFile, *theInputDir, *theOutputDir, *continueOnError); err != nil {
+ logrus.Errorln("binapi-generator:", err)
os.Exit(1)
}
+}
+
+func run(inputFile, inputDir string, outputDir string, continueErr bool) error {
+ if inputFile == "" && inputDir == "" {
+ return fmt.Errorf("input-file or input-dir must be specified")
+ }
- if *inputFile != "" {
+ if inputFile != "" {
// process one input file
- if err := generateFromFile(*inputFile, *outputDir); err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", *inputFile, err)
- os.Exit(1)
+ if err := generateFromFile(inputFile, outputDir); err != nil {
+ return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err)
}
} else {
// process all files in specified directory
- dir, err := filepath.Abs(*inputDir)
+ dir, err := filepath.Abs(inputDir)
if err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: invalid input directory: %v\n", err)
- os.Exit(1)
+ return fmt.Errorf("invalid input directory: %v\n", err)
}
- files, err := getInputFiles(*inputDir)
+ files, err := getInputFiles(inputDir, 1)
if err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: problem getting files from input directory: %v\n", err)
- os.Exit(1)
+ return fmt.Errorf("problem getting files from input directory: %v\n", err)
} else if len(files) == 0 {
- fmt.Fprintf(os.Stderr, "ERROR: no input files found in input directory: %v\n", dir)
- os.Exit(1)
+ return fmt.Errorf("no input files found in input directory: %v\n", dir)
}
for _, file := range files {
- if err := generateFromFile(file, *outputDir); err != nil {
- fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", file, err)
- if *continueOnError {
+ if err := generateFromFile(file, outputDir); err != nil {
+ if continueErr {
+ logrus.Warnf("code generation from %s failed: %v (error ignored)\n", file, err)
continue
+ } else {
+ return fmt.Errorf("code generation from %s failed: %v\n", file, err)
}
- os.Exit(1)
}
}
}
+
+ return nil
}
// getInputFiles returns all input files located in specified directory
-func getInputFiles(inputDir string) (res []string, err error) {
- files, err := ioutil.ReadDir(inputDir)
+func getInputFiles(inputDir string, deep int) (files []string, err error) {
+ entries, err := ioutil.ReadDir(inputDir)
if err != nil {
return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
}
- for _, f := range files {
- if strings.HasSuffix(f.Name(), inputFileExt) {
- res = append(res, filepath.Join(inputDir, f.Name()))
+ for _, e := range entries {
+ if e.IsDir() && deep > 0 {
+ nestedDir := filepath.Join(inputDir, e.Name())
+ if nested, err := getInputFiles(nestedDir, deep-1); err != nil {
+ return nil, err
+ } else {
+ files = append(files, nested...)
+ }
+ } else if strings.HasSuffix(e.Name(), inputFileExt) {
+ files = append(files, filepath.Join(inputDir, e.Name()))
}
}
- return res, nil
+ return files, nil
}
// generateFromFile generates Go package from one input JSON file
func generateFromFile(inputFile, outputDir string) error {
- logf("generating from file: %s", inputFile)
- logf("------------------------------------------------------------")
- defer logf("------------------------------------------------------------")
-
- ctx, err := getContext(inputFile, outputDir)
+ // create generator context
+ ctx, err := newContext(inputFile, outputDir)
if err != nil {
return err
}
+ logf("------------------------------------------------------------")
+ logf("module: %s", ctx.moduleName)
+ logf(" - input: %s", ctx.inputFile)
+ logf(" - output: %s", ctx.outputFile)
+ logf("------------------------------------------------------------")
+
// prepare options
ctx.includeAPIVersion = *includeAPIVer
ctx.includeComments = *includeComments
@@ -123,7 +141,6 @@ func generateFromFile(inputFile, outputDir string) error {
if err != nil {
return fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err)
}
-
// parse JSON data into objects
jsonRoot := new(jsongo.JSONNode)
if err := json.Unmarshal(ctx.inputData, jsonRoot); err != nil {
@@ -156,14 +173,6 @@ func generateFromFile(inputFile, outputDir string) error {
return fmt.Errorf("gofmt failed: %v\n%s", err, string(output))
}
- // count number of lines in generated output file
- cmd = exec.Command("wc", "-l", ctx.outputFile)
- if output, err := cmd.CombinedOutput(); err != nil {
- logf("wc command failed: %v\n%s", err, string(output))
- } else {
- logf("number of generated lines: %s", output)
- }
-
return nil
}
diff --git a/cmd/binapi-generator/objects.go b/cmd/binapi-generator/objects.go
index e3270de..2d5321d 100644
--- a/cmd/binapi-generator/objects.go
+++ b/cmd/binapi-generator/objects.go
@@ -4,6 +4,7 @@ import "fmt"
// Package represents collection of objects parsed from VPP binary API JSON data
type Package struct {
+ Name string
Version string
CRC string
Services []Service
@@ -91,32 +92,33 @@ const (
// printPackage prints all loaded objects for package
func printPackage(pkg *Package) {
+ logf("package: %s %s (%s)", pkg.Name, pkg.Version, pkg.CRC)
if len(pkg.Enums) > 0 {
- logf("loaded %d enums:", len(pkg.Enums))
- for k, enum := range pkg.Enums {
- logf(" - enum #%d\t%+v", k, enum)
+ logf(" %d enums:", len(pkg.Enums))
+ for _, enum := range pkg.Enums {
+ logf(" - %s: %+v", enum.Name, enum)
}
}
if len(pkg.Unions) > 0 {
- logf("loaded %d unions:", len(pkg.Unions))
- for k, union := range pkg.Unions {
- logf(" - union #%d\t%+v", k, union)
+ logf(" %d unions:", len(pkg.Unions))
+ for _, union := range pkg.Unions {
+ logf(" - %s: %+v", union.Name, union)
}
}
if len(pkg.Types) > 0 {
- logf("loaded %d types:", len(pkg.Types))
+ logf(" %d types:", len(pkg.Types))
for _, typ := range pkg.Types {
- logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
+ logf(" - %s (%d fields): %+v", typ.Name, len(typ.Fields), typ)
}
}
if len(pkg.Messages) > 0 {
- logf("loaded %d messages:", len(pkg.Messages))
+ logf(" %d messages:", len(pkg.Messages))
for _, msg := range pkg.Messages {
- logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
+ logf(" - %s (%d fields) %s", msg.Name, len(msg.Fields), msg.CRC)
}
}
if len(pkg.Services) > 0 {
- logf("loaded %d services:", len(pkg.Services))
+ logf(" %d services:", len(pkg.Services))
for _, svc := range pkg.Services {
var info string
if svc.Stream {
@@ -124,7 +126,7 @@ func printPackage(pkg *Package) {
} else if len(svc.Events) > 0 {
info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
}
- logf(" - service: %s - %q -> %q %s", svc.Name, svc.RequestType, svc.ReplyType, info)
+ logf(" - %s: %q -> %q %s", svc.Name, svc.RequestType, svc.ReplyType, info)
}
}
}
diff --git a/cmd/binapi-generator/parse.go b/cmd/binapi-generator/parse.go
index 602b17f..0e4f3ad 100644
--- a/cmd/binapi-generator/parse.go
+++ b/cmd/binapi-generator/parse.go
@@ -74,6 +74,7 @@ const (
// parsePackage parses provided JSON data into objects prepared for code generation
func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
pkg := Package{
+ Name: ctx.packageName,
RefMap: make(map[string]string),
}
@@ -89,16 +90,11 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
}
}
- logf("parsing package %s (version: %s, CRC: %s) contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases",
- ctx.packageName,
- pkg.Version, pkg.CRC,
- jsonRoot.Map(objServices).Len(),
- jsonRoot.Map(objMessages).Len(),
- jsonRoot.Map(objTypes).Len(),
- jsonRoot.Map(objEnums).Len(),
- jsonRoot.Map(objUnions).Len(),
- jsonRoot.Map(objAliases).Len(),
- )
+ logf("parsing package %s (version: %s, CRC: %s)", pkg.Name, pkg.Version, pkg.CRC)
+ logf(" consists of:")
+ for _, key := range jsonRoot.GetKeys() {
+ logf(" - %d %s", jsonRoot.At(key).Len(), key)
+ }
// parse enums
enums := jsonRoot.Map(objEnums)