Format generated Go source code in-process
[govpp.git] / cmd / binapi-generator / main.go
index c66fc4f..fcd85ae 100644 (file)
@@ -19,9 +19,9 @@ import (
        "encoding/json"
        "flag"
        "fmt"
+       "go/format"
        "io/ioutil"
        "os"
-       "os/exec"
        "path/filepath"
        "strings"
 
@@ -32,14 +32,16 @@ import (
 )
 
 var (
-       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.")
+       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.")
@@ -84,14 +86,23 @@ func main() {
        }
 }
 
-func run(inputFile, inputDir string, outputDir string, continueErr bool) error {
+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")
        }
 
+       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 != "" {
                // process one input file
-               if err := generateFromFile(inputFile, outputDir); err != nil {
+               if err := generateFromFile(inputFile, outputDir, typesPkgs); err != nil {
                        return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err)
                }
        } else {
@@ -107,7 +118,7 @@ func run(inputFile, inputDir string, outputDir string, continueErr bool) error {
                        return fmt.Errorf("no input files found in input directory: %v\n", dir)
                }
                for _, file := range files {
-                       if err := generateFromFile(file, outputDir); err != nil {
+                       if err := generateFromFile(file, outputDir, typesPkgs); err != nil {
                                if continueErr {
                                        logrus.Warnf("code generation from %s failed: %v (error ignored)\n", file, err)
                                        continue
@@ -151,7 +162,7 @@ func parseInputJSON(inputData []byte) (*jsongo.Node, error) {
 }
 
 // generateFromFile generates Go package from one input JSON file
-func generateFromFile(inputFile, outputDir string) error {
+func generateFromFile(inputFile, outputDir string, typesPkgs []*context) error {
        // create generator context
        ctx, err := newContext(inputFile, outputDir)
        if err != nil {
@@ -169,6 +180,7 @@ func generateFromFile(inputFile, outputDir string) error {
        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)
@@ -185,10 +197,22 @@ func generateFromFile(inputFile, outputDir string) error {
                return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
        }
 
-       // generate Go package code
+       if len(typesPkgs) > 0 {
+               err = loadTypeAliases(ctx, typesPkgs)
+               if err != nil {
+                       return fmt.Errorf("loading type aliases failed: %v", err)
+               }
+       }
+
+       // generate Go package
        var buf bytes.Buffer
        if err := generatePackage(ctx, &buf); err != nil {
-               return fmt.Errorf("generating code for package %s failed: %v", ctx.packageName, err)
+               return fmt.Errorf("generating Go package for %s failed: %v", ctx.packageName, err)
+       }
+       // format generated source code
+       gosrc, err := format.Source(buf.Bytes())
+       if err != nil {
+               return fmt.Errorf("formatting source code for package %s failed: %v", ctx.packageName, err)
        }
 
        // create output directory
@@ -197,16 +221,113 @@ func generateFromFile(inputFile, outputDir string) error {
                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 {
+       if err := ioutil.WriteFile(ctx.outputFile, gosrc, 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)
-       cmd := exec.Command("gofmt", "-w", ctx.outputFile)
-       if output, err := cmd.CombinedOutput(); err != nil {
-               return fmt.Errorf("gofmt failed: %v\n%s", err, string(output))
+       return nil
+}
+
+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 ctxs, nil
+}
 
+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
 }