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 +++++++++++++++++----------------- cmd/binapi-generator/generate_test.go | 6 +- cmd/binapi-generator/main.go | 97 ++++++------ cmd/binapi-generator/objects.go | 26 +-- cmd/binapi-generator/parse.go | 16 +- 5 files changed, 223 insertions(+), 212 deletions(-) (limited to 'cmd') 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) -- cgit 1.2.3-korg