Support imported type aliases
[govpp.git] / cmd / binapi-generator / main.go
index d46001d..e0e2f08 100644 (file)
@@ -27,12 +27,15 @@ import (
 
        "github.com/bennyscetbun/jsongo"
        "github.com/sirupsen/logrus"
+
+       "git.fd.io/govpp.git/version"
 )
 
 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.")
@@ -40,16 +43,40 @@ var (
        includeBinapiNames = flag.Bool("include-binapi-names", false, "Include binary API names in struct tag.")
 
        continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
+       debugMode       = flag.Bool("debug", os.Getenv("GOVPP_DEBUG") != "", "Enable debug mode.")
 
-       debug = flag.Bool("debug", debugMode, "Enable debug mode.")
+       printVersion = flag.Bool("version", false, "Prints current version and exits.")
 )
 
-var debugMode = os.Getenv("DEBUG_BINAPI_GENERATOR") != ""
-
 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")
        }
 
        if err := run(*theInputFile, *theInputDir, *theOutputDir, *continueOnError); err != nil {
@@ -58,14 +85,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 {
@@ -81,7 +117,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
@@ -116,8 +152,8 @@ func getInputFiles(inputDir string, deep int) (files []string, err error) {
        return files, nil
 }
 
-func parseInputJSON(inputData []byte) (*jsongo.JSONNode, error) {
-       jsonRoot := new(jsongo.JSONNode)
+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)
        }
@@ -125,7 +161,7 @@ func parseInputJSON(inputData []byte) (*jsongo.JSONNode, 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 {
@@ -159,6 +195,13 @@ func generateFromFile(inputFile, outputDir string) error {
                return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
        }
 
+       if len(typesPkgs) > 0 {
+               err = loadTypeAliases(ctx, typesPkgs)
+               if err != nil {
+                       return fmt.Errorf("loading type aliases failed: %v", err)
+               }
+       }
+
        // generate Go package code
        var buf bytes.Buffer
        if err := generatePackage(ctx, &buf); err != nil {
@@ -184,8 +227,111 @@ func generateFromFile(inputFile, outputDir string) error {
        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
+}
+
 func logf(f string, v ...interface{}) {
-       if *debug {
+       if *debugMode {
                logrus.Debugf(f, v...)
        }
 }