Improve doc & fix import ordering
[govpp.git] / cmd / binapi-generator / main.go
index 8bf765b..a730df4 100644 (file)
 package main
 
 import (
-       "bytes"
-       "encoding/json"
        "flag"
        "fmt"
-       "io/ioutil"
        "os"
-       "os/exec"
        "path/filepath"
        "strings"
+       "unicode"
 
-       "github.com/bennyscetbun/jsongo"
        "github.com/sirupsen/logrus"
 
-       "git.fd.io/govpp.git/version"
+       "git.fd.io/govpp.git/binapigen"
+       "git.fd.io/govpp.git/binapigen/vppapi"
+       "git.fd.io/govpp.git/internal/version"
 )
 
-var (
-       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.")
+func init() {
+       flag.Usage = func() {
+               fmt.Fprintf(os.Stderr, "USAGE\n")
+               fmt.Fprintf(os.Stderr, "  Parse API_FILES and generate Go bindings\n")
+               fmt.Fprintf(os.Stderr, "  Provide API_FILES by file name, or with full path including extension.\n")
+               fmt.Fprintf(os.Stderr, "  %s [OPTION] API_FILES\n\n", os.Args[0])
+               fmt.Fprintf(os.Stderr, "OPTIONS\n")
+               flag.PrintDefaults()
+               fmt.Fprintf(os.Stderr, "\nEXAMPLES:\n")
+               fmt.Fprintf(os.Stderr, "  %s \\\n", os.Args[0])
+               fmt.Fprintf(os.Stderr, "    --input-dir=$VPP/build-root/install-vpp-native/vpp/share/vpp/api/ \\\n")
+               fmt.Fprintf(os.Stderr, "    --output-dir=~/output \\\n")
+               fmt.Fprintf(os.Stderr, "    interface ip\n")
+               fmt.Fprintf(os.Stderr, "  Assuming --input-dir contains interface.api.json & ip.api.json\n")
+       }
+}
 
-       printVersion = flag.Bool("version", false, "Prints current version and exits.")
-)
+func printErrorAndExit(msg string) {
+       fmt.Fprintf(os.Stderr, "Error: %s\n\n", msg)
+       flag.Usage()
+       os.Exit(1)
+}
 
 func main() {
+       var (
+               theApiDir        = flag.String("input-dir", vppapi.DefaultDir, "Input directory containing API files. (e.g. )")
+               theOutputDir     = flag.String("output-dir", "binapi", "Output directory where code will be generated.")
+               importPrefix     = flag.String("import-prefix", "", "Prefix imports in the generated go code. \nE.g. other API Files (e.g. api_file.ba.go) will be imported with :\nimport (\n  api_file \"<import-prefix>/api_file\"\n)")
+               generatorPlugins = flag.String("gen", "rpc", "List of generator plugins to run for files.")
+               theInputFile     = flag.String("input-file", "", "DEPRECATED: Use program arguments to define files to generate.")
+
+               printVersion     = flag.Bool("version", false, "Prints version and exits.")
+               debugLog         = flag.Bool("debug", false, "Enable verbose logging.")
+               noVersionInfo    = flag.Bool("no-version-info", false, "Disable version info in generated files.")
+               noSourcePathInfo = flag.Bool("no-source-path-info", false, "Disable source path info in generated files.")
+       )
        flag.Parse()
 
-       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 {
+       if *debugLog {
                logrus.SetLevel(logrus.DebugLevel)
-               logrus.Info("debug mode enabled")
-       }
-
-       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")
        }
 
-       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, typesPkgs); err != nil {
-                       return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err)
+       var filesToGenerate []string
+       if *theInputFile != "" {
+               if flag.NArg() > 0 {
+                       printErrorAndExit("input-file cannot be combined with files to generate in arguments")
                }
+               filesToGenerate = append(filesToGenerate, *theInputFile)
        } else {
-               // process all files in specified directory
-               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 {
-                       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, 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)
-                               }
-                       }
-               }
-       }
-
-       return nil
-}
-
-// getInputFiles returns all input files located in specified directory
-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 _, 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 files, nil
-}
-
-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
-}
-
-// 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
-       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 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 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 {
-               return fmt.Errorf("generating code for package %s failed: %v", ctx.packageName, err)
+               filesToGenerate = append(filesToGenerate, flag.Args()...)
        }
 
-       // 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)
+       opts := binapigen.Options{
+               ImportPrefix:     *importPrefix,
+               OutputDir:        *theOutputDir,
+               NoVersionInfo:    *noVersionInfo,
+               NoSourcePathInfo: *noSourcePathInfo,
        }
-       // 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)
-       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)
+       if opts.OutputDir == "binapi" {
+               if wd, _ := os.Getwd(); filepath.Base(wd) == "binapi" {
+                       opts.OutputDir = "."
                }
-               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
-}
+       apiDir := *theApiDir
+       genPlugins := strings.FieldsFunc(*generatorPlugins, func(c rune) bool {
+               return !unicode.IsLetter(c) && !unicode.IsNumber(c)
+       })
 
-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
+       binapigen.Run(apiDir, filesToGenerate, opts, func(gen *binapigen.Generator) error {
+               for _, file := range gen.Files {
+                       if !file.Generate {
+                               continue
                        }
-                       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,
+                       binapigen.GenerateAPI(gen, file)
+                       for _, p := range genPlugins {
+                               if err := binapigen.RunPlugin(p, gen, file); err != nil {
+                                       return err
                                }
                        }
                }
-       }
-       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 *debugMode {
-               logrus.Debugf(f, v...)
-       }
+               return nil
+       })
 }