Fix parsing API with removed CRC for types and unions
[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         inputFile          = flag.String("input-file", "", "Input file with VPP API in JSON format.")
34         inputDir           = flag.String("input-dir", ".", "Input directory with VPP API files in JSON format.")
35         outputDir          = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
36         includeAPIVer      = flag.Bool("include-apiver", true, "Include APIVersion constant for each module.")
37         includeComments    = flag.Bool("include-comments", false, "Include JSON API source in comments for each object.")
38         includeBinapiNames = flag.Bool("include-binapi-names", false, "Include binary API names in struct tag.")
39         includeServices    = flag.Bool("include-services", false, "Include service interface with client implementation.")
40         continueOnError    = flag.Bool("continue-onerror", false, "Continue with next file on error.")
41         debug              = flag.Bool("debug", debugMode, "Enable debug mode.")
42 )
43
44 var debugMode = os.Getenv("DEBUG_BINAPI_GENERATOR") != ""
45
46 func main() {
47         flag.Parse()
48         if *debug {
49                 logrus.SetLevel(logrus.DebugLevel)
50         }
51
52         if *inputFile == "" && *inputDir == "" {
53                 fmt.Fprintln(os.Stderr, "ERROR: input-file or input-dir must be specified")
54                 os.Exit(1)
55         }
56
57         if *inputFile != "" {
58                 // process one input file
59                 if err := generateFromFile(*inputFile, *outputDir); err != nil {
60                         fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", *inputFile, err)
61                         os.Exit(1)
62                 }
63         } else {
64                 // process all files in specified directory
65                 dir, err := filepath.Abs(*inputDir)
66                 if err != nil {
67                         fmt.Fprintf(os.Stderr, "ERROR: invalid input directory: %v\n", err)
68                         os.Exit(1)
69                 }
70                 files, err := getInputFiles(*inputDir)
71                 if err != nil {
72                         fmt.Fprintf(os.Stderr, "ERROR: problem getting files from input directory: %v\n", err)
73                         os.Exit(1)
74                 } else if len(files) == 0 {
75                         fmt.Fprintf(os.Stderr, "ERROR: no input files found in input directory: %v\n", dir)
76                         os.Exit(1)
77                 }
78                 for _, file := range files {
79                         if err := generateFromFile(file, *outputDir); err != nil {
80                                 fmt.Fprintf(os.Stderr, "ERROR: code generation from %s failed: %v\n", file, err)
81                                 if *continueOnError {
82                                         continue
83                                 }
84                                 os.Exit(1)
85                         }
86                 }
87         }
88 }
89
90 // getInputFiles returns all input files located in specified directory
91 func getInputFiles(inputDir string) (res []string, err error) {
92         files, err := ioutil.ReadDir(inputDir)
93         if err != nil {
94                 return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
95         }
96         for _, f := range files {
97                 if strings.HasSuffix(f.Name(), inputFileExt) {
98                         res = append(res, filepath.Join(inputDir, f.Name()))
99                 }
100         }
101         return res, nil
102 }
103
104 // generateFromFile generates Go package from one input JSON file
105 func generateFromFile(inputFile, outputDir string) error {
106         logf("generating from file: %s", inputFile)
107         logf("------------------------------------------------------------")
108         defer logf("------------------------------------------------------------")
109
110         ctx, err := getContext(inputFile, outputDir)
111         if err != nil {
112                 return err
113         }
114
115         // prepare options
116         ctx.includeAPIVersion = *includeAPIVer
117         ctx.includeComments = *includeComments
118         ctx.includeBinapiNames = *includeBinapiNames
119         ctx.includeServices = *includeServices
120
121         // read API definition from input file
122         ctx.inputData, err = ioutil.ReadFile(ctx.inputFile)
123         if err != nil {
124                 return fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err)
125         }
126
127         // parse JSON data into objects
128         jsonRoot := new(jsongo.JSONNode)
129         if err := json.Unmarshal(ctx.inputData, jsonRoot); err != nil {
130                 return fmt.Errorf("unmarshalling JSON failed: %v", err)
131         }
132         ctx.packageData, err = parsePackage(ctx, jsonRoot)
133         if err != nil {
134                 return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
135         }
136
137         // generate Go package code
138         var buf bytes.Buffer
139         if err := generatePackage(ctx, &buf); err != nil {
140                 return fmt.Errorf("generating code for package %s failed: %v", ctx.packageName, err)
141         }
142
143         // create output directory
144         packageDir := filepath.Dir(ctx.outputFile)
145         if err := os.MkdirAll(packageDir, 0775); err != nil {
146                 return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
147         }
148         // write generated code to output file
149         if err := ioutil.WriteFile(ctx.outputFile, buf.Bytes(), 0666); err != nil {
150                 return fmt.Errorf("writing to output file %s failed: %v", ctx.outputFile, err)
151         }
152
153         // go format the output file (fail probably means the output is not compilable)
154         cmd := exec.Command("gofmt", "-w", ctx.outputFile)
155         if output, err := cmd.CombinedOutput(); err != nil {
156                 return fmt.Errorf("gofmt failed: %v\n%s", err, string(output))
157         }
158
159         // count number of lines in generated output file
160         cmd = exec.Command("wc", "-l", ctx.outputFile)
161         if output, err := cmd.CombinedOutput(); err != nil {
162                 logf("wc command failed: %v\n%s", err, string(output))
163         } else {
164                 logf("number of generated lines: %s", output)
165         }
166
167         return nil
168 }
169
170 func logf(f string, v ...interface{}) {
171         if *debug {
172                 logrus.Debugf(f, v...)
173         }
174 }