1 // Copyright (c) 2020 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.
27 "github.com/sirupsen/logrus"
29 "git.fd.io/govpp.git/binapigen/vppapi"
33 VPPVersion string // version of VPP that produced API files
35 FilesToGenerate []string // list of API files to generate
37 ImportPrefix string // defines import path prefix for importing types
38 ImportTypes bool // generate packages for import types
39 IncludeAPIVersion bool // include constant with API version string
40 IncludeComments bool // include parts of original source in comments
41 IncludeBinapiNames bool // include binary API names as struct tag
42 IncludeServices bool // include service interface with client implementation
43 IncludeVppVersion bool // include info about used VPP version
46 type Generator struct {
50 FilesByPath map[string]*File
51 FilesByName map[string]*File
53 enumsByName map[string]*Enum
54 aliasesByName map[string]*Alias
55 structsByName map[string]*Struct
56 unionsByName map[string]*Union
61 func New(opts Options, apifiles []*vppapi.File) (*Generator, error) {
64 FilesByPath: make(map[string]*File),
65 FilesByName: make(map[string]*File),
66 enumsByName: map[string]*Enum{},
67 aliasesByName: map[string]*Alias{},
68 structsByName: map[string]*Struct{},
69 unionsByName: map[string]*Union{},
72 logrus.Debugf("adding %d VPP API files to generator", len(apifiles))
73 for _, apifile := range apifiles {
74 filename := apifile.Path
76 filename = apifile.Name
78 if _, ok := g.FilesByPath[filename]; ok {
79 return nil, fmt.Errorf("duplicate file name: %q", filename)
81 if _, ok := g.FilesByName[apifile.Name]; ok {
82 return nil, fmt.Errorf("duplicate file: %q", apifile.Name)
85 file, err := newFile(g, apifile)
89 g.Files = append(g.Files, file)
90 g.FilesByPath[filename] = file
91 g.FilesByName[apifile.Name] = file
93 logrus.Debugf("added file %q (path: %v)", apifile.Name, apifile.Path)
94 if len(file.Imports) > 0 {
95 logrus.Debugf(" - %d imports: %v", len(file.Imports), file.Imports)
99 if len(opts.FilesToGenerate) > 0 {
100 logrus.Debugf("Checking %d files to generate: %v", len(opts.FilesToGenerate), opts.FilesToGenerate)
101 for _, genfile := range opts.FilesToGenerate {
102 file, ok := g.FilesByPath[genfile]
104 file, ok = g.FilesByName[genfile]
106 return nil, fmt.Errorf("no API file found for: %v", genfile)
110 if opts.ImportTypes {
111 // generate all imported files
112 for _, impFile := range file.importedFiles(g) {
113 impFile.Generate = true
118 logrus.Debugf("Files to generate not specified, marking all %d files to generate", len(g.Files))
119 for _, file := range g.Files {
124 logrus.Debugf("Resolving imported types")
125 for _, file := range g.Files {
127 // skip resolving for non-generated files
130 var importedFiles []*File
131 for _, impFile := range file.importedFiles(g) {
132 if !impFile.Generate {
133 // exclude imports of non-generated files
136 importedFiles = append(importedFiles, impFile)
138 file.loadTypeImports(g, importedFiles)
144 func (g *Generator) Generate() error {
145 if len(g.genfiles) == 0 {
146 return fmt.Errorf("no files to generate")
149 logrus.Infof("Generating %d files", len(g.genfiles))
150 for _, genfile := range g.genfiles {
151 if err := writeSourceTo(genfile.filename, genfile.Content()); err != nil {
152 return fmt.Errorf("writing source for RPC package %s failed: %v", genfile.filename, err)
158 type GenFile struct {
166 func (g *Generator) NewGenFile(filename string) *GenFile {
171 g.genfiles = append(g.genfiles, f)
175 func (f *GenFile) Content() []byte {
179 func writeSourceTo(outputFile string, b []byte) error {
180 // create output directory
181 packageDir := filepath.Dir(outputFile)
182 if err := os.MkdirAll(packageDir, 0775); err != nil {
183 return fmt.Errorf("creating output dir %s failed: %v", packageDir, err)
186 // format generated source code
187 gosrc, err := format.Source(b)
189 _ = ioutil.WriteFile(outputFile, b, 0666)
190 return fmt.Errorf("formatting source code failed: %v", err)
193 // write generated code to output file
194 if err := ioutil.WriteFile(outputFile, gosrc, 0666); err != nil {
195 return fmt.Errorf("writing to output file %s failed: %v", outputFile, err)
198 lines := bytes.Count(gosrc, []byte("\n"))
199 logf("wrote %d lines (%d bytes) of code to: %q", lines, len(gosrc), outputFile)
204 func listImports(genfile *GenFile) map[string]string {
205 var importPath = genfile.ImportPrefix
206 if importPath == "" {
207 importPath = resolveImportPath(genfile.outputDir)
208 logrus.Debugf("resolved import path: %s", importPath)
210 imports := map[string]string{}
211 for _, imp := range genfile.file.imports {
212 if _, ok := imports[imp]; !ok {
213 imports[imp] = path.Join(importPath, imp)
219 func resolveImportPath(outputDir string) string {
220 absPath, err := filepath.Abs(outputDir)
224 modRoot := findModuleRoot(absPath)
226 logrus.Fatalf("module root not found at: %s", absPath)
228 modPath := findModulePath(path.Join(modRoot, "go.mod"))
230 logrus.Fatalf("module path not found")
232 relDir, err := filepath.Rel(modRoot, absPath)
236 return filepath.Join(modPath, relDir)
239 func findModuleRoot(dir string) (root string) {
243 dir = filepath.Clean(dir)
245 // Look for enclosing go.mod.
247 if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
250 d := filepath.Dir(dir)
260 modulePathRE = regexp.MustCompile(`module[ \t]+([^ \t\r\n]+)`)
263 func findModulePath(file string) string {
264 data, err := ioutil.ReadFile(file)
268 m := modulePathRE.FindSubmatch(data)