package main
import (
- "bufio"
+ "bytes"
"encoding/json"
"flag"
"fmt"
+ "go/format"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"strings"
"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.")
- includeAPIVer = flag.Bool("include-apiver", false, "Include APIVersion constant for each module.")
+ 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.")
- continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
- debug = flag.Bool("debug", debugMode, "Enable debug mode.")
-)
+ importPrefix = flag.String("import-prefix", "", "Define import path prefix to be used to import types.")
-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.")
-var log = logrus.Logger{
- Level: logrus.InfoLevel,
- Formatter: &logrus.TextFormatter{},
- Out: os.Stdout,
-}
+ 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")
+ }
+
+ 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 {
- fmt.Fprintf(os.Stderr, "ERROR: code generation failed: %v\n", err)
- os.Exit(1)
+ 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); 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
}
- ctx.includeAPIVersionCrc = *includeAPIVer
+ 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 input file contents
- ctx.inputData, err = readFile(inputFile)
+ // 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
+ // generate Go package
+ var buf bytes.Buffer
+ if err := generatePackage(ctx, &buf); err != nil {
+ return fmt.Errorf("generating Go package for %s failed: %v", ctx.packageName, 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))
+ // 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)
}
- // 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)
+ // 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, gosrc, 0666); err != nil {
+ return fmt.Errorf("writing to output file %s failed: %v", ctx.outputFile, err)
}
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,
+ }
+ }
+ }
}
-
- return &root, nil
+ 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...)
}
}