From ef471318d66dd2832df4dc929d312f7cd5f7009a Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Wed, 26 Jun 2019 16:28:20 +0200 Subject: 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 --- cmd/binapi-generator/generate.go | 290 ++++++++++++++++++++------------------- 1 file changed, 147 insertions(+), 143 deletions(-) (limited to 'cmd/binapi-generator/generate.go') 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) +} -- cgit 1.2.3-korg