Refactored binapi generator with message encoding
[govpp.git] / binapigen / generator.go
diff --git a/binapigen/generator.go b/binapigen/generator.go
new file mode 100644 (file)
index 0000000..9471462
--- /dev/null
@@ -0,0 +1,172 @@
+//  Copyright (c) 2020 Cisco and/or its affiliates.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at:
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+package binapigen
+
+import (
+       "bytes"
+       "fmt"
+       "go/format"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+
+       "github.com/sirupsen/logrus"
+
+       "git.fd.io/govpp.git/binapigen/vppapi"
+)
+
+type Options struct {
+       VPPVersion string // version of VPP that produced API files
+
+       FilesToGenerate []string // list of API files to generate
+
+       ImportPrefix       string // defines import path prefix for importing types
+       ImportTypes        bool   // generate packages for import types
+       IncludeAPIVersion  bool   // include constant with API version string
+       IncludeComments    bool   // include parts of original source in comments
+       IncludeBinapiNames bool   // include binary API names as struct tag
+       IncludeServices    bool   // include service interface with client implementation
+       IncludeVppVersion  bool   // include info about used VPP version
+}
+
+type Generator struct {
+       Options
+
+       Files       []*File
+       FilesByPath map[string]*File
+       FilesByName map[string]*File
+
+       enumsByName   map[string]*Enum
+       aliasesByName map[string]*Alias
+       structsByName map[string]*Struct
+       unionsByName  map[string]*Union
+
+       genfiles []*GenFile
+}
+
+func New(opts Options, apifiles []*vppapi.File) (*Generator, error) {
+       g := &Generator{
+               Options:       opts,
+               FilesByPath:   make(map[string]*File),
+               FilesByName:   make(map[string]*File),
+               enumsByName:   map[string]*Enum{},
+               aliasesByName: map[string]*Alias{},
+               structsByName: map[string]*Struct{},
+               unionsByName:  map[string]*Union{},
+       }
+
+       logrus.Debugf("adding %d VPP API files to generator", len(apifiles))
+       for _, apifile := range apifiles {
+               filename := apifile.Path
+               if filename == "" {
+                       filename = apifile.Name
+               }
+               if _, ok := g.FilesByPath[filename]; ok {
+                       return nil, fmt.Errorf("duplicate file name: %q", filename)
+               }
+               if _, ok := g.FilesByName[apifile.Name]; ok {
+                       return nil, fmt.Errorf("duplicate file: %q", apifile.Name)
+               }
+
+               file, err := newFile(g, apifile)
+               if err != nil {
+                       return nil, err
+               }
+               g.Files = append(g.Files, file)
+               g.FilesByPath[filename] = file
+               g.FilesByName[apifile.Name] = file
+
+               logrus.Debugf("added file %q (path: %v)", apifile.Name, apifile.Path)
+               if len(file.Imports) > 0 {
+                       logrus.Debugf(" - %d imports: %v", len(file.Imports), file.Imports)
+               }
+       }
+
+       logrus.Debugf("Checking %d files to generate: %v", len(opts.FilesToGenerate), opts.FilesToGenerate)
+       for _, genfile := range opts.FilesToGenerate {
+               file, ok := g.FilesByPath[genfile]
+               if !ok {
+                       file, ok = g.FilesByName[genfile]
+                       if !ok {
+                               return nil, fmt.Errorf("no API file found for: %v", genfile)
+                       }
+               }
+               file.Generate = true
+               if opts.ImportTypes {
+                       for _, impFile := range file.importedFiles(g) {
+                               impFile.Generate = true
+                       }
+               }
+       }
+
+       logrus.Debugf("Resolving imported types")
+       for _, file := range g.Files {
+               if !file.Generate {
+                       continue
+               }
+               importedFiles := file.importedFiles(g)
+               file.loadTypeImports(g, importedFiles)
+       }
+
+       return g, nil
+}
+
+func (g *Generator) Generate() error {
+       if len(g.genfiles) == 0 {
+               return fmt.Errorf("no files to generate")
+       }
+
+       logrus.Infof("Generating %d files", len(g.genfiles))
+       for _, genfile := range g.genfiles {
+               if err := writeSourceTo(genfile.filename, genfile.buf.Bytes()); err != nil {
+                       return fmt.Errorf("writing source for RPC package %s failed: %v", genfile.filename, err)
+               }
+       }
+       return nil
+}
+
+func (g *Generator) NewGenFile(filename string) *GenFile {
+       f := &GenFile{
+               Generator: g,
+               filename:  filename,
+       }
+       g.genfiles = append(g.genfiles, f)
+       return f
+}
+
+func writeSourceTo(outputFile string, b []byte) error {
+       // create output directory
+       packageDir := filepath.Dir(outputFile)
+       if err := os.MkdirAll(packageDir, 0775); err != nil {
+               return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
+       }
+
+       // format generated source code
+       gosrc, err := format.Source(b)
+       if err != nil {
+               _ = ioutil.WriteFile(outputFile, b, 0666)
+               return fmt.Errorf("formatting source code failed: %v", err)
+       }
+
+       // write generated code to output file
+       if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil {
+               return fmt.Errorf("writing to output file %s failed: %v", outputFile, err)
+       }
+
+       lines := bytes.Count(gosrc, []byte("\n"))
+       logf("wrote %d lines (%d bytes) of code to: %q", lines, len(gosrc), outputFile)
+
+       return nil
+}