1 // Copyright (c) 2018 Cisco and/or its affiliates.
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:
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
28 "github.com/bennyscetbun/jsongo"
29 "github.com/sirupsen/logrus"
31 "git.fd.io/govpp.git/version"
35 theInputFile = flag.String("input-file", "", "Input file with VPP API in JSON format.")
36 theInputTypes = flag.String("input-types", "", "Types input file with VPP API in JSON format. (split by comma)")
37 theInputDir = flag.String("input-dir", "/usr/share/vpp/api", "Input directory with VPP API files in JSON format.")
38 theOutputDir = flag.String("output-dir", ".", "Output directory where package folders will be generated.")
40 includeAPIVer = flag.Bool("include-apiver", true, "Include APIVersion constant for each module.")
41 includeServices = flag.Bool("include-services", true, "Include RPC service api and client implementation.")
42 includeComments = flag.Bool("include-comments", false, "Include JSON API source in comments for each object.")
43 includeBinapiNames = flag.Bool("include-binapi-names", false, "Include binary API names in struct tag.")
44 importPrefix = flag.String("import-prefix", "", "Define import path prefix to be used to import types.")
46 continueOnError = flag.Bool("continue-onerror", false, "Continue with next file on error.")
47 debugMode = flag.Bool("debug", os.Getenv("GOVPP_DEBUG") != "", "Enable debug mode.")
49 printVersion = flag.Bool("version", false, "Prints current version and exits.")
61 switch cmd := flag.Arg(0); cmd {
63 fmt.Fprintln(os.Stdout, version.Verbose())
67 fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
74 fmt.Fprintln(os.Stdout, version.Info())
79 logrus.SetLevel(logrus.DebugLevel)
80 logrus.Info("debug mode enabled")
83 if err := run(*theInputFile, *theInputDir, *theOutputDir, *continueOnError); err != nil {
84 logrus.Errorln("binapi-generator:", err)
89 func run(inputFile, inputDir string, outputDir string, continueErr bool) (err error) {
90 if inputFile == "" && inputDir == "" {
91 return fmt.Errorf("input-file or input-dir must be specified")
94 var typesPkgs []*context
95 if *theInputTypes != "" {
96 types := strings.Split(*theInputTypes, ",")
97 typesPkgs, err = loadTypesPackages(types...)
99 return fmt.Errorf("loading types input failed: %v", err)
104 // process one input file
105 if err := generateFromFile(inputFile, outputDir, typesPkgs); err != nil {
106 return fmt.Errorf("code generation from %s failed: %v\n", inputFile, err)
109 // process all files in specified directory
110 dir, err := filepath.Abs(inputDir)
112 return fmt.Errorf("invalid input directory: %v\n", err)
114 files, err := getInputFiles(inputDir, 1)
116 return fmt.Errorf("problem getting files from input directory: %v\n", err)
117 } else if len(files) == 0 {
118 return fmt.Errorf("no input files found in input directory: %v\n", dir)
120 for _, file := range files {
121 if err := generateFromFile(file, outputDir, typesPkgs); err != nil {
123 logrus.Warnf("code generation from %s failed: %v (error ignored)\n", file, err)
126 return fmt.Errorf("code generation from %s failed: %v\n", file, err)
135 // getInputFiles returns all input files located in specified directory
136 func getInputFiles(inputDir string, deep int) (files []string, err error) {
137 entries, err := ioutil.ReadDir(inputDir)
139 return nil, fmt.Errorf("reading directory %s failed: %v", inputDir, err)
141 for _, e := range entries {
142 if e.IsDir() && deep > 0 {
143 nestedDir := filepath.Join(inputDir, e.Name())
144 if nested, err := getInputFiles(nestedDir, deep-1); err != nil {
147 files = append(files, nested...)
149 } else if strings.HasSuffix(e.Name(), inputFileExt) {
150 files = append(files, filepath.Join(inputDir, e.Name()))
156 func parseInputJSON(inputData []byte) (*jsongo.Node, error) {
157 jsonRoot := new(jsongo.Node)
158 if err := json.Unmarshal(inputData, jsonRoot); err != nil {
159 return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
164 // generateFromFile generates Go package from one input JSON file
165 func generateFromFile(inputFile, outputDir string, typesPkgs []*context) error {
166 // create generator context
167 ctx, err := newContext(inputFile, outputDir)
172 logf("------------------------------------------------------------")
173 logf("module: %s", ctx.moduleName)
174 logf(" - input: %s", ctx.inputFile)
175 logf(" - output: %s", ctx.outputFile)
176 logf("------------------------------------------------------------")
179 ctx.includeAPIVersion = *includeAPIVer
180 ctx.includeComments = *includeComments
181 ctx.includeBinapiNames = *includeBinapiNames
182 ctx.includeServices = *includeServices
183 ctx.importPrefix = *importPrefix
185 // read API definition from input file
186 ctx.inputData, err = ioutil.ReadFile(ctx.inputFile)
188 return fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err)
190 // parse JSON data into objects
191 jsonRoot, err := parseInputJSON(ctx.inputData)
193 return fmt.Errorf("parsing JSON input failed: %v", err)
195 ctx.packageData, err = parsePackage(ctx, jsonRoot)
197 return fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
200 if len(typesPkgs) > 0 {
201 err = loadTypeAliases(ctx, typesPkgs)
203 return fmt.Errorf("loading type aliases failed: %v", err)
207 // generate Go package
209 if err := generatePackage(ctx, &buf); err != nil {
210 return fmt.Errorf("generating Go package for %s failed: %v", ctx.packageName, err)
212 // format generated source code
213 gosrc, err := format.Source(buf.Bytes())
215 return fmt.Errorf("formatting source code for package %s failed: %v", ctx.packageName, err)
218 // create output directory
219 packageDir := filepath.Dir(ctx.outputFile)
220 if err := os.MkdirAll(packageDir, 0775); err != nil {
221 return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
223 // write generated code to output file
224 if err := ioutil.WriteFile(ctx.outputFile, gosrc, 0666); err != nil {
225 return fmt.Errorf("writing to output file %s failed: %v", ctx.outputFile, err)
231 func loadTypesPackages(types ...string) ([]*context, error) {
233 for _, inputFile := range types {
234 // create generator context
235 ctx, err := newContext(inputFile, "")
239 // read API definition from input file
240 ctx.inputData, err = ioutil.ReadFile(ctx.inputFile)
242 return nil, fmt.Errorf("reading input file %s failed: %v", ctx.inputFile, err)
244 // parse JSON data into objects
245 jsonRoot, err := parseInputJSON(ctx.inputData)
247 return nil, fmt.Errorf("parsing JSON input failed: %v", err)
249 ctx.packageData, err = parsePackage(ctx, jsonRoot)
251 return nil, fmt.Errorf("parsing package %s failed: %v", ctx.packageName, err)
253 ctxs = append(ctxs, ctx)
258 func loadTypeAliases(ctx *context, typesCtxs []*context) error {
259 for _, t := range ctx.packageData.Types {
260 for _, c := range typesCtxs {
261 if _, ok := ctx.packageData.Imports[t.Name]; ok {
264 for _, at := range c.packageData.Types {
265 if at.Name != t.Name {
268 if len(at.Fields) != len(t.Fields) {
271 ctx.packageData.Imports[t.Name] = Import{
272 Package: c.packageName,
277 for _, t := range ctx.packageData.Aliases {
278 for _, c := range typesCtxs {
279 if _, ok := ctx.packageData.Imports[t.Name]; ok {
282 for _, at := range c.packageData.Aliases {
283 if at.Name != t.Name {
286 if at.Length != t.Length {
289 if at.Type != t.Type {
292 ctx.packageData.Imports[t.Name] = Import{
293 Package: c.packageName,
298 for _, t := range ctx.packageData.Enums {
299 for _, c := range typesCtxs {
300 if _, ok := ctx.packageData.Imports[t.Name]; ok {
303 for _, at := range c.packageData.Enums {
304 if at.Name != t.Name {
307 if at.Type != t.Type {
310 ctx.packageData.Imports[t.Name] = Import{
311 Package: c.packageName,
316 for _, t := range ctx.packageData.Unions {
317 for _, c := range typesCtxs {
318 if _, ok := ctx.packageData.Imports[t.Name]; ok {
321 for _, at := range c.packageData.Unions {
322 if at.Name != t.Name {
325 ctx.packageData.Imports[t.Name] = Import{
326 Package: c.packageName,
334 func logf(f string, v ...interface{}) {
336 logrus.Debugf(f, v...)