Improvements for binapi-generator and support VPP 19.04 in statsclient
[govpp.git] / cmd / binapi-generator / main.go
1 // Copyright (c) 2018 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package main
16
17 import (
18         "bytes"
19         "encoding/json"
20         "flag"
21         "fmt"
22         "io/ioutil"
23         "os"
24         "os/exec"
25         "path/filepath"
26         "strings"
27
28         "github.com/bennyscetbun/jsongo"
29         "github.com/sirupsen/logrus"
30 )
31
32 var (
33         theInputFile = flag.String("input-file", "", "Input file with VPP API in JSON format.")
34         theInputDir  = flag.String("input-dir", "/usr/share/vpp/api", "Input directory with VPP API files in JSON format.")
35         theOutputDir = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
36
37         includeAPIVer      = flag.Bool("include-apiver", true, "Include APIVersion constant for each module.")
38         includeServices    = flag.Bool("include-services", true, "Include RPC service api and client implementation.")
39         includeComments    = flag.Bool("include-comments", false, "Include JSON API source in comments for each object.")
40         includeBinapiNames = flag.Bool("include-binapi-names", false, "Include binary API names in struct tag.")
41
42         continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
43
44         debug = flag.Bool("debug", debugMode, "Enable debug mode.")
45 )
46
47 var debugMode = os.Getenv("DEBUG_BINAPI_GENERATOR") != ""
48
49 func main() {
50         flag.Parse()
51         if *debug {
52                 logrus.SetLevel(logrus.DebugLevel)
53         }
54
55         if err := run(*theInputFile, *theInputDir, *theOutputDir, *continueOnError); err != nil {
56                 logrus.Errorln("binapi-generator:", err)
57                 os.Exit(1)
58         }
59 }
60
61 func run(inputFile, inputDir string, outputDir string, continueErr bool) error {
62         if inputFile == "" && inputDir == "" {
63                 return fmt.Errorf("input-file or input-dir must be specified")
64         }
65
66         if inputFile != "" {
67                 // process one input file
68                 if err := generateFromFile(inputFile, outputDir); err != nil {
69                         return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err)
70                 }
71         } else {
72                 // process all files in specified directory
73                 dir, err := filepath.Abs(inputDir)
74                 if err != nil {
75                         return fmt.Errorf("invalid input directory: %v\n", err)
76                 }
77                 files, err := getInputFiles(inputDir, 1)
78                 if err != nil {
79                         return fmt.Errorf("problem getting files from input directory: %v\n", err)
80                 } else if len(files) == 0 {
81                         return fmt.Errorf("no input files found in input directory: %v\n", dir)
82                 }
83                 for _, file := range files {
84                         if err := generateFromFile(file, outputDir); err != nil {
85                                 if continueErr {
86                                         logrus.Warnf("code generation from %s failed: %v (error ignored)\n", file, err)
87                                         continue
88                                 } else {
89                                         return fmt.Errorf("code generation from %s failed: %v\n", file, err)
90                                 }
91                         }
92                 }
93         }
94
95         return nil
96 }
97
98 // getInputFiles returns all input files located in specified directory
99 func getInputFiles(inputDir string, deep int) (files []string, err error) {
100         entries, err := ioutil.ReadDir(inputDir)
101         if err != nil {
102                 return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
103         }
104         for _, e := range entries {
105                 if e.IsDir() && deep > 0 {
106                         nestedDir := filepath.Join(inputDir, e.Name())
107                         if nested, err := getInputFiles(nestedDir, deep-1); err != nil {
108                                 return nil, err
109                         } else {
110                                 files = append(files, nested...)
111                         }
112                 } else if strings.HasSuffix(e.Name(), inputFileExt) {
113                         files = append(files, filepath.Join(inputDir, e.Name()))
114                 }
115         }
116         return files, nil
117 }
118
119 // generateFromFile generates Go package from one input JSON file
120 func generateFromFile(inputFile, outputDir string) error {
121         // create generator context
122         ctx, err := newContext(inputFile, outputDir)
123         if err != nil {
124                 return err
125         }
126
127         logf("------------------------------------------------------------")
128         logf("module: %s", ctx.moduleName)
129         logf(" - input: %s", ctx.inputFile)
130         logf(" - output: %s", ctx.outputFile)
131         logf("------------------------------------------------------------")
132
133         // prepare options
134         ctx.includeAPIVersion = *includeAPIVer
135         ctx.includeComments = *includeComments
136         ctx.includeBinapiNames = *includeBinapiNames
137         ctx.includeServices = *includeServices
138
139         // read API definition from input file
140         ctx.inputData, err = ioutil.ReadFile(ctx.inputFile)
141         if err != nil {
142                 return fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err)
143         }
144         // parse JSON data into objects
145         jsonRoot := new(jsongo.JSONNode)
146         if err := json.Unmarshal(ctx.inputData, jsonRoot); err != nil {
147                 return fmt.Errorf("unmarshalling JSON failed: %v", err)
148         }
149         ctx.packageData, err = parsePackage(ctx, jsonRoot)
150         if err != nil {
151                 return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
152         }
153
154         // generate Go package code
155         var buf bytes.Buffer
156         if err := generatePackage(ctx, &buf); err != nil {
157                 return fmt.Errorf("generating code for package %s failed: %v", ctx.packageName, err)
158         }
159
160         // create output directory
161         packageDir := filepath.Dir(ctx.outputFile)
162         if err := os.MkdirAll(packageDir, 0775); err != nil {
163                 return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
164         }
165         // write generated code to output file
166         if err := ioutil.WriteFile(ctx.outputFile, buf.Bytes(), 0666); err != nil {
167                 return fmt.Errorf("writing to output file %s failed: %v", ctx.outputFile, err)
168         }
169
170         // go format the output file (fail probably means the output is not compilable)
171         cmd := exec.Command("gofmt", "-w", ctx.outputFile)
172         if output, err := cmd.CombinedOutput(); err != nil {
173                 return fmt.Errorf("gofmt failed: %v\n%s", err, string(output))
174         }
175
176         return nil
177 }
178
179 func logf(f string, v ...interface{}) {
180         if *debug {
181                 logrus.Debugf(f, v...)
182         }
183 }