X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=cmd%2Fbinapi-generator%2Fmain.go;h=8bf765b3831c3c719336c249cc4930cccfb6fad0;hb=d4d3b9c6fa67fe454f8d8adc7502b3b645ecab10;hp=b73a6993e41475e85db81ab57c982a847921afa4;hpb=08ddeac03fd3832d44a3dfb48ee85ecd95d2b388;p=govpp.git diff --git a/cmd/binapi-generator/main.go b/cmd/binapi-generator/main.go index b73a699..8bf765b 100644 --- a/cmd/binapi-generator/main.go +++ b/cmd/binapi-generator/main.go @@ -15,7 +15,7 @@ package main import ( - "bufio" + "bytes" "encoding/json" "flag" "fmt" @@ -27,122 +27,197 @@ import ( "github.com/bennyscetbun/jsongo" "github.com/sirupsen/logrus" + + "git.fd.io/govpp.git/version" ) var ( - inputFile = flag.String("input-file", "", "Input JSON file.") - inputDir = flag.String("input-dir", ".", "Input directory with JSON files.") - outputDir = flag.String("output-dir", ".", "Output directory where package folders will be generated.") - includeAPIVer = flag.Bool("include-apiver", false, "Whether to include VlAPIVersion in generated file.") - debug = flag.Bool("debug", false, "Turn on debug mode.") - continueOnError = flag.Bool("continue-onerror", false, "Wheter to continue with next file on error.") + theInputFile = flag.String("input-file", "", "Input file with VPP API in JSON format.") + theInputTypes = flag.String("input-types", "", "Types input file with VPP API in JSON format. (split by comma)") + 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.") + importPrefix = flag.String("import-prefix", "", "Define import path prefix to be used to import types.") + + continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.") + debugMode = flag.Bool("debug", os.Getenv("GOVPP_DEBUG") != "", "Enable debug mode.") + + printVersion = flag.Bool("version", false, "Prints current version and exits.") ) -func init() { +func main() { flag.Parse() - if *debug { + + if flag.NArg() > 1 { + flag.Usage() + os.Exit(1) + } + + if flag.NArg() > 0 { + switch cmd := flag.Arg(0); cmd { + case "version": + fmt.Fprintln(os.Stdout, version.Verbose()) + os.Exit(0) + + default: + fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd) + flag.Usage() + os.Exit(2) + } + } + + if *printVersion { + fmt.Fprintln(os.Stdout, version.Info()) + os.Exit(0) + } + + if *debugMode { logrus.SetLevel(logrus.DebugLevel) + logrus.Info("debug mode enabled") } -} -func logf(f string, v ...interface{}) { - if *debug { - logrus.Debugf(f, v...) + if err := run(*theInputFile, *theInputDir, *theOutputDir, *continueOnError); err != nil { + logrus.Errorln("binapi-generator:", err) + os.Exit(1) } } -var log = logrus.Logger{ - Level: logrus.InfoLevel, - Formatter: &logrus.TextFormatter{}, - Out: os.Stdout, -} +func run(inputFile, inputDir string, outputDir string, continueErr bool) (err error) { + if inputFile == "" && inputDir == "" { + return fmt.Errorf("input-file or input-dir must be specified") + } -func main() { - if *inputFile == "" && *inputDir == "" { - fmt.Fprintln(os.Stderr, "ERROR: input-file or input-dir must be specified") - os.Exit(1) + var typesPkgs []*context + if *theInputTypes != "" { + types := strings.Split(*theInputTypes, ",") + typesPkgs, err = loadTypesPackages(types...) + if err != nil { + return fmt.Errorf("loading types input failed: %v", err) + } } - 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, typesPkgs); err != nil { + return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err) } } else { // process all files in specified directory - files, err := getInputFiles(*inputDir) + dir, err := filepath.Abs(inputDir) + if err != nil { + return fmt.Errorf("invalid input directory: %v\n", err) + } + files, err := getInputFiles(inputDir, 1) if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: code generation failed: %v\n", err) - os.Exit(1) + return fmt.Errorf("problem getting files from input directory: %v\n", err) + } else if len(files) == 0 { + 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, typesPkgs); 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: %q", inputFile) - defer logf("--------------------------------------") +func parseInputJSON(inputData []byte) (*jsongo.Node, error) { + jsonRoot := new(jsongo.Node) + if err := json.Unmarshal(inputData, jsonRoot); err != nil { + return nil, fmt.Errorf("unmarshalling JSON failed: %v", err) + } + return jsonRoot, nil +} - ctx, err := getContext(inputFile, outputDir) +// generateFromFile generates Go package from one input JSON file +func generateFromFile(inputFile, outputDir string, typesPkgs []*context) error { + // create generator context + ctx, err := newContext(inputFile, outputDir) if err != nil { return err } - // read input file contents - ctx.inputData, err = readFile(inputFile) + 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 + ctx.includeBinapiNames = *includeBinapiNames + ctx.includeServices = *includeServices + ctx.importPrefix = *importPrefix + + // read API definition from input file + ctx.inputData, err = ioutil.ReadFile(ctx.inputFile) if err != nil { - return err + return fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err) } // parse JSON data into objects - jsonRoot, err := parseJSON(ctx.inputData) + jsonRoot, err := parseInputJSON(ctx.inputData) if err != nil { - return err + return fmt.Errorf("parsing JSON input failed: %v", err) } ctx.packageData, err = parsePackage(ctx, jsonRoot) if err != nil { - return err + return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err) } - // create output directory - packageDir := filepath.Dir(ctx.outputFile) - if err := os.MkdirAll(packageDir, 0777); err != nil { - return fmt.Errorf("creating output directory %q failed: %v", packageDir, err) - } - // open output file - f, err := os.Create(ctx.outputFile) - if err != nil { - return fmt.Errorf("creating output file %q failed: %v", ctx.outputFile, err) + if len(typesPkgs) > 0 { + err = loadTypeAliases(ctx, typesPkgs) + if err != nil { + return fmt.Errorf("loading type aliases failed: %v", err) + } } - defer f.Close() // generate Go package code - w := bufio.NewWriter(f) - if err := generatePackage(ctx, w); err != nil { - return err + var buf bytes.Buffer + if err := generatePackage(ctx, &buf); err != nil { + return fmt.Errorf("generating code for package %s failed: %v", ctx.packageName, err) + } + + // create output directory + packageDir := filepath.Dir(ctx.outputFile) + if err := os.MkdirAll(packageDir, 0775); err != nil { + return fmt.Errorf("creating output dir %s failed: %v", packageDir, err) + } + // write generated code to output file + if err := ioutil.WriteFile(ctx.outputFile, buf.Bytes(), 0666); err != nil { + return fmt.Errorf("writing to output file %s failed: %v", ctx.outputFile, err) } // go format the output file (fail probably means the output is not compilable) @@ -151,34 +226,114 @@ 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 { - log.Warnf("wc command failed: %v\n%s", err, string(output)) - } else { - logf("generated lines: %s", output) - } - return nil } -// readFile reads content of a file into memory -func readFile(inputFile string) ([]byte, error) { - inputData, err := ioutil.ReadFile(inputFile) - if err != nil { - return nil, fmt.Errorf("reading data from file failed: %v", err) +func loadTypesPackages(types ...string) ([]*context, error) { + var ctxs []*context + for _, inputFile := range types { + // create generator context + ctx, err := newContext(inputFile, "") + if err != nil { + return nil, err + } + // read API definition from input file + ctx.inputData, err = ioutil.ReadFile(ctx.inputFile) + if err != nil { + return nil, fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err) + } + // parse JSON data into objects + jsonRoot, err := parseInputJSON(ctx.inputData) + if err != nil { + return nil, fmt.Errorf("parsing JSON input failed: %v", err) + } + ctx.packageData, err = parsePackage(ctx, jsonRoot) + if err != nil { + return nil, fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err) + } + ctxs = append(ctxs, ctx) } - - return inputData, nil + return ctxs, nil } -// parseJSON parses a JSON data into an in-memory tree -func parseJSON(inputData []byte) (*jsongo.JSONNode, error) { - root := jsongo.JSONNode{} - - if err := json.Unmarshal(inputData, &root); err != nil { - return nil, fmt.Errorf("unmarshalling JSON failed: %v", err) +func loadTypeAliases(ctx *context, typesCtxs []*context) error { + for _, t := range ctx.packageData.Types { + for _, c := range typesCtxs { + if _, ok := ctx.packageData.Imports[t.Name]; ok { + break + } + for _, at := range c.packageData.Types { + if at.Name != t.Name { + continue + } + if len(at.Fields) != len(t.Fields) { + continue + } + ctx.packageData.Imports[t.Name] = Import{ + Package: c.packageName, + } + } + } + } + for _, t := range ctx.packageData.Aliases { + for _, c := range typesCtxs { + if _, ok := ctx.packageData.Imports[t.Name]; ok { + break + } + for _, at := range c.packageData.Aliases { + if at.Name != t.Name { + continue + } + if at.Length != t.Length { + continue + } + if at.Type != t.Type { + continue + } + ctx.packageData.Imports[t.Name] = Import{ + Package: c.packageName, + } + } + } } + for _, t := range ctx.packageData.Enums { + for _, c := range typesCtxs { + if _, ok := ctx.packageData.Imports[t.Name]; ok { + break + } + for _, at := range c.packageData.Enums { + if at.Name != t.Name { + continue + } + if at.Type != t.Type { + continue + } + ctx.packageData.Imports[t.Name] = Import{ + Package: c.packageName, + } + } + } + } + for _, t := range ctx.packageData.Unions { + for _, c := range typesCtxs { + if _, ok := ctx.packageData.Imports[t.Name]; ok { + break + } + for _, at := range c.packageData.Unions { + if at.Name != t.Name { + continue + } + ctx.packageData.Imports[t.Name] = Import{ + Package: c.packageName, + } + } + } + } + return nil +} - return &root, nil +func logf(f string, v ...interface{}) { + if *debugMode { + logrus.Debugf(f, v...) + } }