Refactored binapi generator with message encoding
[govpp.git] / binapigen / generator.go
1 //  Copyright (c) 2020 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 binapigen
16
17 import (
18         "bytes"
19         "fmt"
20         "go/format"
21         "io/ioutil"
22         "os"
23         "path/filepath"
24
25         "github.com/sirupsen/logrus"
26
27         "git.fd.io/govpp.git/binapigen/vppapi"
28 )
29
30 type Options struct {
31         VPPVersion string // version of VPP that produced API files
32
33         FilesToGenerate []string // list of API files to generate
34
35         ImportPrefix       string // defines import path prefix for importing types
36         ImportTypes        bool   // generate packages for import types
37         IncludeAPIVersion  bool   // include constant with API version string
38         IncludeComments    bool   // include parts of original source in comments
39         IncludeBinapiNames bool   // include binary API names as struct tag
40         IncludeServices    bool   // include service interface with client implementation
41         IncludeVppVersion  bool   // include info about used VPP version
42 }
43
44 type Generator struct {
45         Options
46
47         Files       []*File
48         FilesByPath map[string]*File
49         FilesByName map[string]*File
50
51         enumsByName   map[string]*Enum
52         aliasesByName map[string]*Alias
53         structsByName map[string]*Struct
54         unionsByName  map[string]*Union
55
56         genfiles []*GenFile
57 }
58
59 func New(opts Options, apifiles []*vppapi.File) (*Generator, error) {
60         g := &Generator{
61                 Options:       opts,
62                 FilesByPath:   make(map[string]*File),
63                 FilesByName:   make(map[string]*File),
64                 enumsByName:   map[string]*Enum{},
65                 aliasesByName: map[string]*Alias{},
66                 structsByName: map[string]*Struct{},
67                 unionsByName:  map[string]*Union{},
68         }
69
70         logrus.Debugf("adding %d VPP API files to generator", len(apifiles))
71         for _, apifile := range apifiles {
72                 filename := apifile.Path
73                 if filename == "" {
74                         filename = apifile.Name
75                 }
76                 if _, ok := g.FilesByPath[filename]; ok {
77                         return nil, fmt.Errorf("duplicate file name: %q", filename)
78                 }
79                 if _, ok := g.FilesByName[apifile.Name]; ok {
80                         return nil, fmt.Errorf("duplicate file: %q", apifile.Name)
81                 }
82
83                 file, err := newFile(g, apifile)
84                 if err != nil {
85                         return nil, err
86                 }
87                 g.Files = append(g.Files, file)
88                 g.FilesByPath[filename] = file
89                 g.FilesByName[apifile.Name] = file
90
91                 logrus.Debugf("added file %q (path: %v)", apifile.Name, apifile.Path)
92                 if len(file.Imports) > 0 {
93                         logrus.Debugf(" - %d imports: %v", len(file.Imports), file.Imports)
94                 }
95         }
96
97         logrus.Debugf("Checking %d files to generate: %v", len(opts.FilesToGenerate), opts.FilesToGenerate)
98         for _, genfile := range opts.FilesToGenerate {
99                 file, ok := g.FilesByPath[genfile]
100                 if !ok {
101                         file, ok = g.FilesByName[genfile]
102                         if !ok {
103                                 return nil, fmt.Errorf("no API file found for: %v", genfile)
104                         }
105                 }
106                 file.Generate = true
107                 if opts.ImportTypes {
108                         for _, impFile := range file.importedFiles(g) {
109                                 impFile.Generate = true
110                         }
111                 }
112         }
113
114         logrus.Debugf("Resolving imported types")
115         for _, file := range g.Files {
116                 if !file.Generate {
117                         continue
118                 }
119                 importedFiles := file.importedFiles(g)
120                 file.loadTypeImports(g, importedFiles)
121         }
122
123         return g, nil
124 }
125
126 func (g *Generator) Generate() error {
127         if len(g.genfiles) == 0 {
128                 return fmt.Errorf("no files to generate")
129         }
130
131         logrus.Infof("Generating %d files", len(g.genfiles))
132         for _, genfile := range g.genfiles {
133                 if err := writeSourceTo(genfile.filename, genfile.buf.Bytes()); err != nil {
134                         return fmt.Errorf("writing source for RPC package %s failed: %v", genfile.filename, err)
135                 }
136         }
137         return nil
138 }
139
140 func (g *Generator) NewGenFile(filename string) *GenFile {
141         f := &GenFile{
142                 Generator: g,
143                 filename:  filename,
144         }
145         g.genfiles = append(g.genfiles, f)
146         return f
147 }
148
149 func writeSourceTo(outputFile string, b []byte) error {
150         // create output directory
151         packageDir := filepath.Dir(outputFile)
152         if err := os.MkdirAll(packageDir, 0775); err != nil {
153                 return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
154         }
155
156         // format generated source code
157         gosrc, err := format.Source(b)
158         if err != nil {
159                 _ = ioutil.WriteFile(outputFile, b, 0666)
160                 return fmt.Errorf("formatting source code failed: %v", err)
161         }
162
163         // write generated code to output file
164         if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil {
165                 return fmt.Errorf("writing to output file %s failed: %v", outputFile, err)
166         }
167
168         lines := bytes.Count(gosrc, []byte("\n"))
169         logf("wrote %d lines (%d bytes) of code to: %q", lines, len(gosrc), outputFile)
170
171         return nil
172 }