Support imported type aliases
[govpp.git] / cmd / binapi-generator / main.go
index 75926e1..e0e2f08 100644 (file)
@@ -27,83 +27,153 @@ import (
 
        "github.com/bennyscetbun/jsongo"
        "github.com/sirupsen/logrus"
+
+       "git.fd.io/govpp.git/version"
 )
 
 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.")
+       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.")
-       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.")
-)
 
-var debugMode = os.Getenv("DEBUG_BINAPI_GENERATOR") != ""
+       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 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 *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) (err error) {
+       if inputFile == "" && inputDir == "" {
+               return fmt.Errorf("input-file or input-dir must be specified")
+       }
 
-       if *inputFile != "" {
+       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 {
-                       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: %s", inputFile)
-       logf("------------------------------------------------------------")
-       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
        }
 
+       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
@@ -115,17 +185,23 @@ 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 {
-               return fmt.Errorf("unmarshalling JSON failed: %v", err)
+       jsonRoot, err := parseInputJSON(ctx.inputData)
+       if err != nil {
+               return fmt.Errorf("parsing JSON input failed: %v", err)
        }
        ctx.packageData, err = parsePackage(ctx, jsonRoot)
        if err != nil {
                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 {
@@ -134,7 +210,7 @@ func generateFromFile(inputFile, outputDir string) error {
 
        // create output directory
        packageDir := filepath.Dir(ctx.outputFile)
-       if err := os.MkdirAll(packageDir, 06); err != nil {
+       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
@@ -148,19 +224,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 {
-               logf("wc command failed: %v\n%s", err, string(output))
-       } else {
-               logf("number of generated lines: %s", 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
 }
 
 func logf(f string, v ...interface{}) {
-       if *debug {
+       if *debugMode {
                logrus.Debugf(f, v...)
        }
 }