From 62f5161e3224f3e7c4e4ec4fb3050677f194caa8 Mon Sep 17 00:00:00 2001 From: Nathan Skrzypczak Date: Wed, 27 Jul 2022 13:27:35 +0200 Subject: [PATCH] Move plugins to own module & improve apigen CLI This patch moves the binapigenerator plugins to their own package. - It capitalizes the methods they leverage. - It changes the way plugins are called, passing the whole generator at once and letting the plugin themselves loop on the files. This allow for more variety in filtering, aggregation, etc... This patch also proposes an evolution to the binapigen CLI in order to make it more user-friendly for newcomers as well as build pipelines based on go:generate For now two usecases are in the picture: - Building from a directory containing .json files e.g. 'binapigen --api /usr/share/vpp/api --output ./myapp/bindings --filter interface,ip' - Building from a cloned local VPP e.g. 'binapigen --vpp ~/vpp --output ./myapp/binding' Signed-off-by: Nathan Skrzypczak Change-Id: I5b0b2ade40ab80c9e91c2a422f8c193b232d9830 --- binapigen/binapigen.go | 2 +- binapigen/binapigen_test.go | 67 +++++++----- binapigen/gen_encoding.go | 2 +- binapigen/generate.go | 9 +- binapigen/generate_test.go | 36 +++---- binapigen/generator.go | 103 ++++++++++++++---- binapigen/generator_test.go | 7 +- binapigen/plugin.go | 53 +++++---- binapigen/{ => plugins}/gen_http.go | 34 +++--- binapigen/{ => plugins}/gen_rpc.go | 58 +++++----- binapigen/plugins/plugins.go | 15 +++ binapigen/run.go | 155 --------------------------- binapigen/types.go | 2 +- binapigen/{vppapi => }/util.go | 139 ++++++++++++++++++++++-- binapigen/vppapi/integration_test.go | 2 +- binapigen/vppapi/vppapi.go | 36 ++++--- binapigen/vppapi/vppapi_test.go | 12 +-- cmd/binapi-generator/main.go | 119 ++++++++++---------- cmd/govpp/main.go | 2 +- go.mod | 5 +- go.sum | 10 ++ 21 files changed, 483 insertions(+), 385 deletions(-) rename binapigen/{ => plugins}/gen_http.go (79%) rename binapigen/{ => plugins}/gen_rpc.go (80%) create mode 100644 binapigen/plugins/plugins.go delete mode 100644 binapigen/run.go rename binapigen/{vppapi => }/util.go (51%) diff --git a/binapigen/binapigen.go b/binapigen/binapigen.go index c671b643..6d77ab3d 100644 --- a/binapigen/binapigen.go +++ b/binapigen/binapigen.go @@ -380,7 +380,7 @@ func getMsgType(m vppapi.Message) (msgType, error) { return typ, nil } -func getRetvalField(m *Message) *Field { +func GetRetvalField(m *Message) *Field { for _, field := range m.Fields { if field.Name == fieldRetval { return field diff --git a/binapigen/binapigen_test.go b/binapigen/binapigen_test.go index c9bfba70..d977613e 100644 --- a/binapigen/binapigen_test.go +++ b/binapigen/binapigen_test.go @@ -15,43 +15,56 @@ package binapigen import ( + "os" + "path/filepath" "testing" . "github.com/onsi/gomega" - - "go.fd.io/govpp/binapigen/vppapi" ) +var sampleJson = `{ + "types": [], + "messages": [], + "unions": [], + "enums": [], + "enumflags": [], + "services": {}, + "options": { + "version": "1.7.0" + }, + "aliases": {}, + "vl_api_version": "0x12345678", + "imports": [], + "counters": [], + "paths": [] +}` + func TestGenerator(t *testing.T) { - tests := []struct { - name string - file *vppapi.File - expectPackage string - }{ - {name: "vpe", file: &vppapi.File{ - Name: "vpe", - Path: "/usr/share/vpp/api/core/vpe.api.json", - CRC: "0x12345678", - }, - expectPackage: "vpe", - }, + RegisterTestingT(t) + + dir, err := os.MkdirTemp("", "govpp-test") + if err != nil { + t.Fatal(err) } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - RegisterTestingT(t) + defer os.RemoveAll(dir) - apiFiles := []*vppapi.File{test.file} + file := filepath.Join(dir, "vpe.api.json") + if err := os.WriteFile(file, []byte(sampleJson), 0666); err != nil { + t.Fatal(err) + } - gen, err := New(Options{ - ImportPrefix: "test", - }, apiFiles, nil) - Expect(err).ToNot(HaveOccurred(), "unexpected generator error: %v", err) + os.Setenv(VPPVersionEnvVar, "test-version") + gen, err := New(Options{ + ApiDir: dir, + FileFilter: []string{file}, + ImportPrefix: "test", + }) + Expect(err).ToNot(HaveOccurred(), "unexpected generator error: %v", err) - Expect(gen.Files).To(HaveLen(1)) - Expect(gen.Files[0].PackageName).To(BeEquivalentTo(test.expectPackage)) - Expect(gen.Files[0].GoImportPath).To(BeEquivalentTo("test/" + test.expectPackage)) - }) - } + Expect(gen.Files).To(HaveLen(1)) + Expect(gen.Files[0].PackageName).To(BeEquivalentTo("vpe")) + Expect(gen.Files[0].GoImportPath).To(BeEquivalentTo("test/vpe")) + Expect(gen.Files[0].Desc.CRC).To(BeEquivalentTo("0x12345678")) } func TestSanitize(t *testing.T) { diff --git a/binapigen/gen_encoding.go b/binapigen/gen_encoding.go index ca1e8486..51c64f71 100644 --- a/binapigen/gen_encoding.go +++ b/binapigen/gen_encoding.go @@ -310,7 +310,7 @@ func decodeField(g *GenFile, field *Field, name string, getFieldName func(string if field.Length > 0 { g.P("for ", index, " := 0; ", index, " < ", field.Length, ";", index, "++ {") } else if field.SizeFrom != "" { - g.P(name, " = make(", getFieldType(g, field), ", ", sizeFromName, ")") + g.P(name, " = make(", GetFieldType(g, field), ", ", sizeFromName, ")") g.P("for ", index, " := 0; ", index, " < len(", name, ");", index, "++ {") } name = fmt.Sprintf("%s[%s]", name, index) diff --git a/binapigen/generate.go b/binapigen/generate.go index c68ee673..4e998c2d 100644 --- a/binapigen/generate.go +++ b/binapigen/generate.go @@ -60,8 +60,7 @@ func GenerateAPI(gen *Generator, file *File) *GenFile { logf("----------------------------") filename := path.Join(file.FilenamePrefix, file.Desc.Name+".ba.go") - g := gen.NewGenFile(filename, file.GoImportPath) - g.file = file + g := gen.NewGenFile(filename, file) g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") if !gen.opts.NoVersionInfo { @@ -317,7 +316,7 @@ func genUnion(g *GenFile, union *Union) { // generate field comments g.P("// ", union.GoName, " can be one of:") for _, field := range union.Fields { - g.P("// - ", field.GoName, " *", getFieldType(g, field)) + g.P("// - ", field.GoName, " *", GetFieldType(g, field)) } // generate data field @@ -377,7 +376,7 @@ func genField(g *GenFile, fields []*Field, i int) { logf(" gen FIELD[%d] %s (%s) - type: %q (array: %v/%v)", i, field.GoName, field.Name, field.Type, field.Array, field.Length) - gotype := getFieldType(g, field) + gotype := GetFieldType(g, field) tags := structTags{ "binapi": fieldTagBinapi(field), "json": fieldTagJson(field), @@ -520,7 +519,7 @@ func genMessageMethods(g *GenFile, msg *Message) { if msg.msgType == msgTypeReply || msg.msgType == msgTypeEvent { // GetRetVal method g.P("func (m *", msg.GoIdent.GoName, ") GetRetVal() error {") - if getRetvalField(msg) != nil { + if GetRetvalField(msg) != nil { g.P(" return api.RetvalToVPPApiError(int32(m.Retval))") } else { g.P(" return nil") diff --git a/binapigen/generate_test.go b/binapigen/generate_test.go index 2a946367..a5843bfa 100644 --- a/binapigen/generate_test.go +++ b/binapigen/generate_test.go @@ -21,26 +21,19 @@ import ( . "github.com/onsi/gomega" "go.fd.io/govpp/binapi/ip_types" - "go.fd.io/govpp/binapigen/vppapi" ) const testOutputDir = "test_output_dir" -func GenerateFromFile(file string, opts Options) error { - apifile, err := vppapi.ParseFile(file) +func GenerateFromFile(filename string) error { + os.Setenv(VPPVersionEnvVar, "test-version") + gen, err := New(Options{ + OutputDir: testOutputDir, + ApiDir: filename, + }) if err != nil { return err } - gen, err := New(opts, []*vppapi.File{apifile}, nil) - if err != nil { - return err - } - for _, file := range gen.Files { - if !file.Generate { - continue - } - GenerateAPI(gen, file) - } if err = gen.Generate(); err != nil { return err } @@ -53,8 +46,7 @@ func TestGenerateFromFileACL(t *testing.T) { // remove directory created during test defer os.RemoveAll(testOutputDir) - opts := Options{OutputDir: testOutputDir} - err := GenerateFromFile("vppapi/testdata/acl.api.json", opts) + err := GenerateFromFile("vppapi/testdata/acl.api.json") Expect(err).ShouldNot(HaveOccurred()) fileInfo, err := os.Stat(testOutputDir + "/acl/acl.ba.go") Expect(err).ShouldNot(HaveOccurred()) @@ -68,8 +60,7 @@ func TestGenerateFromFileIP(t *testing.T) { // remove directory created during test defer os.RemoveAll(testOutputDir) - opts := Options{OutputDir: testOutputDir} - err := GenerateFromFile("vppapi/testdata/ip.api.json", opts) + err := GenerateFromFile("vppapi/testdata/ip.api.json") Expect(err).ShouldNot(HaveOccurred()) fileInfo, err := os.Stat(testOutputDir + "/ip/ip.ba.go") Expect(err).ShouldNot(HaveOccurred()) @@ -80,17 +71,15 @@ func TestGenerateFromFileIP(t *testing.T) { func TestGenerateFromFileInputError(t *testing.T) { RegisterTestingT(t) - opts := Options{OutputDir: testOutputDir} - err := GenerateFromFile("vppapi/testdata/nonexisting.json", opts) + err := GenerateFromFile("vppapi/testdata/nonexisting.json") Expect(err).Should(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unsupported")) + Expect(err.Error()).To(ContainSubstring("vppapi/testdata/nonexisting.json does not exist")) } func TestGenerateFromFileReadJsonError(t *testing.T) { RegisterTestingT(t) - opts := Options{OutputDir: testOutputDir} - err := GenerateFromFile("vppapi/testdata/input-read-json-error.json", opts) + err := GenerateFromFile("vppapi/testdata/input-read-json-error.json") Expect(err).Should(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("unsupported")) } @@ -106,8 +95,7 @@ func TestGenerateFromFileGeneratePackageError(t *testing.T) { os.RemoveAll(testOutputDir) }() - opts := Options{OutputDir: testOutputDir} - err := GenerateFromFile("vppapi/testdata/input-generate-error.json", opts) + err := GenerateFromFile("vppapi/testdata/input-generate-error.json") Expect(err).Should(HaveOccurred()) } diff --git a/binapigen/generator.go b/binapigen/generator.go index 126c1636..c1e08f4e 100644 --- a/binapigen/generator.go +++ b/binapigen/generator.go @@ -34,17 +34,42 @@ import ( "go.fd.io/govpp/binapigen/vppapi" ) +var Logger = logrus.New() + +func init() { + if debug := os.Getenv("DEBUG_GOVPP"); strings.Contains(debug, "binapigen") { + Logger.SetLevel(logrus.DebugLevel) + logrus.SetLevel(logrus.DebugLevel) + } +} + +func logf(f string, v ...interface{}) { + Logger.Debugf(f, v...) +} + +func (*Generator) Logf(f string, v ...interface{}) { + logf(f, v...) +} + +type Options struct { + OutputDir string // output directory for generated files + ImportPrefix string // prefix for import paths + NoVersionInfo bool // disables generating version info + NoSourcePathInfo bool // disables the 'source: /path' comment + ActivePluginNames []string + ApiDir string + FileFilter []string +} + type Generator struct { Files []*File FilesByName map[string]*File FilesByPath map[string]*File opts Options - apifiles []*vppapi.File vppVersion string - filesToGen []string - genfiles []*GenFile + genfiles []*GenFile enumsByName map[string]*Enum aliasesByName map[string]*Alias @@ -53,39 +78,61 @@ type Generator struct { messagesByName map[string]*Message } -func New(opts Options, apiFiles []*vppapi.File, filesToGen []string) (*Generator, error) { - gen := &Generator{ +func (g *Generator) GetMessagesByName(name string) *Message { + msg, ok := g.messagesByName[name] + if !ok { + logrus.Fatalf("no message named %v found", name) + } + return msg +} + +func (g *Generator) GetOpts() *Options { return &g.opts } + +func New(opts Options) (gen *Generator, err error) { + if opts.ImportPrefix == "" { + opts.ImportPrefix, err = ResolveImportPath(opts.OutputDir) + if err != nil { + return nil, fmt.Errorf("cannot resolve import path for output dir %s: %w", opts.OutputDir, err) + } + logrus.Debugf("resolved import path prefix: %s", opts.ImportPrefix) + } + + apiFiles, err := vppapi.ParseDir(opts.ApiDir) + if err != nil { + return nil, fmt.Errorf("cannot parse directory %s for api files: %w", opts.ApiDir, err) + } + + gen = &Generator{ FilesByName: make(map[string]*File), FilesByPath: make(map[string]*File), opts: opts, - apifiles: apiFiles, - filesToGen: filesToGen, enumsByName: map[string]*Enum{}, aliasesByName: map[string]*Alias{}, structsByName: map[string]*Struct{}, unionsByName: map[string]*Union{}, messagesByName: map[string]*Message{}, + vppVersion: ResolveVPPVersion(opts.ApiDir), } // Normalize API files - SortFilesByImports(gen.apifiles) + SortFilesByImports(apiFiles) for _, apiFile := range apiFiles { - RemoveImportedTypes(gen.apifiles, apiFile) + RemoveImportedTypes(apiFiles, apiFile) SortFileObjectsByName(apiFile) } // prepare package names and import paths packageNames := make(map[string]GoPackageName) importPaths := make(map[string]GoImportPath) - for _, apifile := range gen.apifiles { + for _, apifile := range apiFiles { filename := getFilename(apifile) packageNames[filename] = cleanPackageName(apifile.Name) importPaths[filename] = GoImportPath(path.Join(gen.opts.ImportPrefix, baseName(apifile.Name))) } - logrus.Debugf("adding %d VPP API files to generator", len(gen.apifiles)) + logrus.Debugf("adding %d VPP API files to generator", len(apiFiles)) - for _, apifile := range gen.apifiles { + for _, apifile := range apiFiles { if _, ok := gen.FilesByName[apifile.Name]; ok { return nil, fmt.Errorf("duplicate file: %q", apifile.Name) } @@ -103,9 +150,9 @@ func New(opts Options, apiFiles []*vppapi.File, filesToGen []string) (*Generator } // mark files for generation - if len(gen.filesToGen) > 0 { - logrus.Debugf("Checking %d files to generate: %v", len(gen.filesToGen), gen.filesToGen) - for _, genFile := range gen.filesToGen { + if len(opts.FileFilter) > 0 { + logrus.Debugf("Checking %d files to generate: %v", len(opts.FileFilter), opts.FileFilter) + for _, genFile := range opts.FileFilter { markGen := func(file *File) { file.Generate = true // generate all imported files @@ -142,6 +189,19 @@ func getFilename(file *vppapi.File) string { } func (g *Generator) Generate() error { + for _, file := range g.Files { + if !file.Generate { + continue + } + GenerateAPI(g, file) + } + for _, pluginName := range g.opts.ActivePluginNames { + err := g.RunPlugin(pluginName) + if err != nil { + return fmt.Errorf("error running plugin %s %s", pluginName, err) + } + } + if len(g.genfiles) == 0 { return fmt.Errorf("no files to generate") } @@ -164,18 +224,21 @@ type GenFile struct { gen *Generator file *File filename string - goImportPath GoImportPath buf bytes.Buffer manualImports map[GoImportPath]bool packageNames map[GoImportPath]GoPackageName } +func (*GenFile) Logf(f string, v ...interface{}) { + logf(f, v...) +} + // NewGenFile creates new generated file with -func (g *Generator) NewGenFile(filename string, importPath GoImportPath) *GenFile { +func (g *Generator) NewGenFile(filename string, file *File) *GenFile { f := &GenFile{ gen: g, + file: file, filename: filename, - goImportPath: importPath, manualImports: make(map[GoImportPath]bool), packageNames: make(map[GoImportPath]GoPackageName), } @@ -183,6 +246,8 @@ func (g *Generator) NewGenFile(filename string, importPath GoImportPath) *GenFil return f } +func (g *GenFile) GetFile() *File { return g.file } + func (g *GenFile) Write(p []byte) (n int, err error) { return g.buf.Write(p) } @@ -192,7 +257,7 @@ func (g *GenFile) Import(importPath GoImportPath) { } func (g *GenFile) GoIdent(ident GoIdent) string { - if ident.GoImportPath == g.goImportPath { + if ident.GoImportPath == g.file.GoImportPath { return ident.GoName } if packageName, ok := g.packageNames[ident.GoImportPath]; ok { diff --git a/binapigen/generator_test.go b/binapigen/generator_test.go index 40b70fa8..560460ed 100644 --- a/binapigen/generator_test.go +++ b/binapigen/generator_test.go @@ -17,16 +17,17 @@ package binapigen import ( "bufio" "fmt" - . "github.com/onsi/gomega" "os" "strings" "testing" + + . "github.com/onsi/gomega" ) func TestGoModule(t *testing.T) { const expected = "go.fd.io/govpp/binapi" - impPath, err := resolveImportPath("../binapi") + impPath, err := ResolveImportPath("../binapi") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -70,7 +71,7 @@ func TestBinapiUnionSizes(t *testing.T) { Expect(err).ToNot(HaveOccurred()) }() - err := GenerateFromFile("vppapi/testdata/union.api.json", Options{OutputDir: testOutputDir}) + err := GenerateFromFile("vppapi/testdata/union.api.json") Expect(err).ShouldNot(HaveOccurred()) file, err := os.Open(testOutputDir + "/union/union.ba.go") diff --git a/binapigen/plugin.go b/binapigen/plugin.go index b57cc68f..77b51582 100644 --- a/binapigen/plugin.go +++ b/binapigen/plugin.go @@ -14,38 +14,55 @@ package binapigen -import "fmt" +import ( + "fmt" + "plugin" + "strings" +) type Plugin struct { Name string - GenerateFile func(*Generator, *File) *GenFile + GenerateFile func(*Generator) } -var Plugins = map[string]*Plugin{} -var plugins []*Plugin +var registeredPlugins = map[string]*Plugin{} -func RegisterPlugin(name string, genfn func(*Generator, *File) *GenFile) { +func (g *Generator) RunPlugin(name string) error { + if plugin, ok := registeredPlugins[name]; ok { + plugin.GenerateFile(g) + return nil + } + // Name can also be the path to an external plugin. + plg, err := plugin.Open(name) + if err != nil { + return fmt.Errorf("plugin %s not found (%s)", name, err) + } + pluginGenerateFile, err := plg.Lookup("GenerateFile") + if err != nil { + return err + } + pluginGenerateFile.(func(*Generator))(g) + + return nil +} + +func RegisterPlugin(name string, genfn func(*Generator)) { if name == "" { panic("plugin name empty") } - for _, p := range plugins { - if p.Name == name { - panic("duplicate plugin name: " + name) - } + if _, ok := registeredPlugins[name]; ok { + panic("plugin name reused") } - plugin := &Plugin{ + registeredPlugins[name] = &Plugin{ Name: name, GenerateFile: genfn, } - plugins = append(plugins, plugin) - Plugins[name] = plugin } -func RunPlugin(name string, gen *Generator, file *File) error { - p, ok := Plugins[name] - if !ok { - return fmt.Errorf("plugin not found: %q", name) +func GetAvailablePluginNames() string { + s := make([]string, 0) + for k := range registeredPlugins { + s = append(s, k) } - p.GenerateFile(gen, file) - return nil + return strings.Join(s, ",") } diff --git a/binapigen/gen_http.go b/binapigen/plugins/gen_http.go similarity index 79% rename from binapigen/gen_http.go rename to binapigen/plugins/gen_http.go index 4c9697eb..2f303e1a 100644 --- a/binapigen/gen_http.go +++ b/binapigen/plugins/gen_http.go @@ -12,36 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -package binapigen +package plugins import ( "path" "strconv" + + "go.fd.io/govpp/binapigen" ) func init() { - RegisterPlugin("http", GenerateHTTP) + binapigen.RegisterPlugin("http", GenerateHTTP) } // library dependencies const ( - httpPkg = GoImportPath("net/http") - ioutilPkg = GoImportPath("io/ioutil") - jsonPkg = GoImportPath("encoding/json") + httpPkg = binapigen.GoImportPath("net/http") + ioutilPkg = binapigen.GoImportPath("io/ioutil") + jsonPkg = binapigen.GoImportPath("encoding/json") ) -func GenerateHTTP(gen *Generator, file *File) *GenFile { +func GenerateHTTP(gen *binapigen.Generator) { + for _, file := range gen.Files { + if !file.Generate { + continue + } + generateOneHTTP(gen, file) + } +} + +func generateOneHTTP(gen *binapigen.Generator, file *binapigen.File) *binapigen.GenFile { if file.Service == nil { return nil } - logf("----------------------------") - logf(" Generate HTTP - %s", file.Desc.Name) - logf("----------------------------") + gen.Logf("----------------------------") + gen.Logf(" Generate HTTP - %s", file.Desc.Name) + gen.Logf("----------------------------") filename := path.Join(file.FilenamePrefix, file.Desc.Name+"_http.ba.go") - g := gen.NewGenFile(filename, file.GoImportPath) - g.file = file + g := gen.NewGenFile(filename, file) // generate file header g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") @@ -57,7 +67,7 @@ func GenerateHTTP(gen *Generator, file *File) *GenFile { return g } -func genHTTPHandler(g *GenFile, svc *Service) { +func genHTTPHandler(g *binapigen.GenFile, svc *binapigen.Service) { // generate handler constructor g.P("func HTTPHandler(rpc ", serviceApiName, ") ", httpPkg.Ident("Handler"), " {") g.P(" mux := ", httpPkg.Ident("NewServeMux"), "()") diff --git a/binapigen/gen_rpc.go b/binapigen/plugins/gen_rpc.go similarity index 80% rename from binapigen/gen_rpc.go rename to binapigen/plugins/gen_rpc.go index 33602ccc..89b48da5 100644 --- a/binapigen/gen_rpc.go +++ b/binapigen/plugins/gen_rpc.go @@ -12,23 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -package binapigen +package plugins import ( "fmt" "path" - "github.com/sirupsen/logrus" + "go.fd.io/govpp/binapigen" ) func init() { - RegisterPlugin("rpc", GenerateRPC) + binapigen.RegisterPlugin("rpc", GenerateRPC) } // library dependencies const ( - contextPkg = GoImportPath("context") - ioPkg = GoImportPath("io") + contextPkg = binapigen.GoImportPath("context") + ioPkg = binapigen.GoImportPath("io") + govppApiPkg = binapigen.GoImportPath("go.fd.io/govpp/api") + fmtPkg = binapigen.GoImportPath("fmt") ) // generated names @@ -42,18 +44,26 @@ const ( //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var ) -func GenerateRPC(gen *Generator, file *File) *GenFile { +func GenerateRPC(gen *binapigen.Generator) { + for _, file := range gen.Files { + if !file.Generate { + continue + } + generateOneRPC(gen, file) + } +} + +func generateOneRPC(gen *binapigen.Generator, file *binapigen.File) *binapigen.GenFile { if file.Service == nil { return nil } - logf("----------------------------") - logf(" Generate RPC - %s", file.Desc.Name) - logf("----------------------------") + gen.Logf("----------------------------") + gen.Logf(" Generate RPC - %s", file.Desc.Name) + gen.Logf("----------------------------") filename := path.Join(file.FilenamePrefix, file.Desc.Name+"_rpc.ba.go") - g := gen.NewGenFile(filename, file.GoImportPath) - g.file = file + g := gen.NewGenFile(filename, file) // generate file header g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") @@ -63,15 +73,15 @@ func GenerateRPC(gen *Generator, file *File) *GenFile { // generate RPC service if len(file.Service.RPCs) > 0 { - genService(g, file.Service) + genService(g, file.Service, gen) } return g } -func genService(g *GenFile, svc *Service) { +func genService(g *binapigen.GenFile, svc *binapigen.Service, gen *binapigen.Generator) { // generate comment - g.P("// ", serviceApiName, " defines RPC service ", g.file.Desc.Name, ".") + g.P("// ", serviceApiName, " defines RPC service ", g.GetFile().Desc.Name, ".") // generate service interface g.P("type ", serviceApiName, " interface {") @@ -93,24 +103,18 @@ func genService(g *GenFile, svc *Service) { g.P("}") g.P() - msgControlPingReply, ok := g.gen.messagesByName["control_ping_reply"] - if !ok { - logrus.Fatalf("no message for %v", "control_ping_reply") - } - msgControlPing, ok := g.gen.messagesByName["control_ping"] - if !ok { - logrus.Fatalf("no message for %v", "control_ping") - } + msgControlPingReply := gen.GetMessagesByName("control_ping_reply") + msgControlPing := gen.GetMessagesByName("control_ping") for _, rpc := range svc.RPCs { - logf(" gen RPC: %v (%s)", rpc.GoName, rpc.VPP.Request) + g.Logf(" gen RPC: %v (%s)", rpc.GoName, rpc.VPP.Request) g.P("func (c *", serviceImplName, ") ", rpcMethodSignature(g, rpc), " {") if rpc.VPP.Stream { streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, rpc.GoName) streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, rpc.GoName) - var msgReply, msgDetails *Message + var msgReply, msgDetails *binapigen.Message if rpc.MsgStream != nil { msgDetails = rpc.MsgStream msgReply = rpc.MsgReply @@ -161,8 +165,8 @@ func genService(g *GenFile, svc *Service) { g.P("out := new(", rpc.MsgReply.GoIdent, ")") g.P("err := c.conn.Invoke(ctx, in, out)") g.P("if err != nil { return nil, err }") - if retvalField := getRetvalField(rpc.MsgReply); retvalField != nil { - if fieldType := getFieldType(g, retvalField); fieldType == "int32" { + if retvalField := binapigen.GetRetvalField(rpc.MsgReply); retvalField != nil { + if fieldType := binapigen.GetFieldType(g, retvalField); fieldType == "int32" { g.P("return out, ", govppApiPkg.Ident("RetvalToVPPApiError"), "(out.", retvalField.GoName, ")") } else { g.P("return out, ", govppApiPkg.Ident("RetvalToVPPApiError"), "(int32(out.", retvalField.GoName, "))") @@ -202,7 +206,7 @@ func genService(g *GenFile, svc *Service) { g.P() } -func rpcMethodSignature(g *GenFile, rpc *RPC) string { +func rpcMethodSignature(g *binapigen.GenFile, rpc *binapigen.RPC) string { s := rpc.GoName + "(ctx " + g.GoIdent(contextPkg.Ident("Context")) s += ", in *" + g.GoIdent(rpc.MsgRequest.GoIdent) + ") (" if rpc.VPP.Stream { diff --git a/binapigen/plugins/plugins.go b/binapigen/plugins/plugins.go new file mode 100644 index 00000000..31f98f27 --- /dev/null +++ b/binapigen/plugins/plugins.go @@ -0,0 +1,15 @@ +// Copyright (c) 2022 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 plugins diff --git a/binapigen/run.go b/binapigen/run.go deleted file mode 100644 index 08af97b6..00000000 --- a/binapigen/run.go +++ /dev/null @@ -1,155 +0,0 @@ -// 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 ( - "fmt" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/sirupsen/logrus" - - "go.fd.io/govpp/binapigen/vppapi" -) - -type Options struct { - OutputDir string // output directory for generated files - ImportPrefix string // prefix for import paths - NoVersionInfo bool // disables generating version info - NoSourcePathInfo bool // disables the 'source: /path' comment -} - -func Run(apiDir string, filesToGenerate []string, opts Options, f func(*Generator) error) { - if err := run(apiDir, filesToGenerate, opts, f); err != nil { - fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err) - os.Exit(1) - } -} - -func run(apiDir string, filesToGenerate []string, opts Options, fn func(*Generator) error) error { - apiFiles, err := vppapi.ParseDir(apiDir) - if err != nil { - return err - } - - if opts.ImportPrefix == "" { - opts.ImportPrefix, err = resolveImportPath(opts.OutputDir) - if err != nil { - return fmt.Errorf("cannot resolve import path for output dir %s: %w", opts.OutputDir, err) - } - logrus.Debugf("resolved import path prefix: %s", opts.ImportPrefix) - } - - gen, err := New(opts, apiFiles, filesToGenerate) - if err != nil { - return err - } - - gen.vppVersion = vppapi.ResolveVPPVersion(apiDir) - if gen.vppVersion == "" { - gen.vppVersion = "unknown" - } - - if fn == nil { - GenerateDefault(gen) - } else { - if err := fn(gen); err != nil { - return err - } - } - if err = gen.Generate(); err != nil { - return err - } - - return nil -} - -func GenerateDefault(gen *Generator) { - for _, file := range gen.Files { - if !file.Generate { - continue - } - GenerateAPI(gen, file) - GenerateRPC(gen, file) - } -} - -var Logger = logrus.New() - -func init() { - if debug := os.Getenv("DEBUG_GOVPP"); strings.Contains(debug, "binapigen") { - Logger.SetLevel(logrus.DebugLevel) - logrus.SetLevel(logrus.DebugLevel) - } -} - -func logf(f string, v ...interface{}) { - Logger.Debugf(f, v...) -} - -// resolveImportPath tries to resolve import path for a directory. -func resolveImportPath(dir string) (string, error) { - absPath, err := filepath.Abs(dir) - if err != nil { - return "", err - } - modRoot := findGoModuleRoot(absPath) - if modRoot == "" { - return "", err - } - modPath, err := readModulePath(path.Join(modRoot, "go.mod")) - if err != nil { - return "", err - } - relDir, err := filepath.Rel(modRoot, absPath) - if err != nil { - return "", err - } - return filepath.Join(modPath, relDir), nil -} - -// findGoModuleRoot looks for enclosing Go module. -func findGoModuleRoot(dir string) (root string) { - dir = filepath.Clean(dir) - for { - if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { - return dir - } - d := filepath.Dir(dir) - if d == dir { - break - } - dir = d - } - return "" -} - -var modulePathRE = regexp.MustCompile(`module[ \t]+([^ \t\r\n]+)`) - -// readModulePath reads module path from go.mod file. -func readModulePath(gomod string) (string, error) { - data, err := os.ReadFile(gomod) - if err != nil { - return "", err - } - m := modulePathRE.FindSubmatch(data) - if m == nil { - return "", err - } - return string(m[1]), nil -} diff --git a/binapigen/types.go b/binapigen/types.go index 483e8209..e175f877 100644 --- a/binapigen/types.go +++ b/binapigen/types.go @@ -106,7 +106,7 @@ func fieldGoType(g *GenFile, field *Field) string { return t } -func getFieldType(g *GenFile, field *Field) string { +func GetFieldType(g *GenFile, field *Field) string { gotype := fieldGoType(g, field) if field.Array { switch gotype { diff --git a/binapigen/vppapi/util.go b/binapigen/util.go similarity index 51% rename from binapigen/vppapi/util.go rename to binapigen/util.go index 63f32c4f..99ef8eca 100644 --- a/binapigen/vppapi/util.go +++ b/binapigen/util.go @@ -12,22 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vppapi +package binapigen import ( "fmt" "os" "os/exec" "path" + "path/filepath" + "regexp" "strings" + "unicode" "github.com/sirupsen/logrus" + "go.fd.io/govpp/binapigen/vppapi" ) const ( VPPVersionEnvVar = "VPP_VERSION" VPPDirEnvVar = "VPP_DIR" versionScriptPath = "./src/scripts/version" + generatedJsonPath = "build-root/install-vpp-native/vpp/share/vpp/api/" ) // ResolveVPPVersion resolves version of the VPP for target directory. @@ -45,14 +50,14 @@ func ResolveVPPVersion(apidir string) string { version, err := GetVPPVersionInstalled() if err != nil { logrus.Warnf("resolving VPP version from installed package failed: %v", err) - } else { + } else if version != "" { logrus.Infof("resolved VPP version from installed package: %v", version) return version } } // check if inside VPP repo - repoDir, err := findGitRepoRootDir(apidir) + repoDir, err := FindGitRepoRootDir(apidir) if err != nil { logrus.Warnf("checking VPP git repo failed: %v", err) } else { @@ -60,7 +65,7 @@ func ResolveVPPVersion(apidir string) string { version, err := GetVPPVersionRepo(repoDir) if err != nil { logrus.Warnf("resolving VPP version from version script failed: %v", err) - } else { + } else if version != "" { logrus.Infof("resolved VPP version from version script: %v", version) return version } @@ -73,7 +78,7 @@ func ResolveVPPVersion(apidir string) string { } logrus.Warnf("VPP version could not be resolved, you can set it manually using %s env var", VPPVersionEnvVar) - return "" + return "unknown" } // GetVPPVersionInstalled retrieves VPP version of installed package using dpkg-query. @@ -101,7 +106,7 @@ func GetVPPVersionRepo(repoDir string) (string, error) { return strings.TrimSpace(string(out)), nil } -func findGitRepoRootDir(dir string) (string, error) { +func FindGitRepoRootDir(dir string) (string, error) { if conf := os.Getenv(VPPDirEnvVar); conf != "" { logrus.Infof("VPP directory was manually set to %q via %s env var", conf, VPPDirEnvVar) return conf, nil @@ -114,3 +119,125 @@ func findGitRepoRootDir(dir string) (string, error) { } return strings.TrimSpace(string(out)), nil } + +// resolveImportPath tries to resolve import path for a directory. +func ResolveImportPath(dir string) (string, error) { + absPath, err := filepath.Abs(dir) + if err != nil { + return "", err + } + modRoot := FindGoModuleRoot(absPath) + if modRoot == "" { + return "", err + } + modPath, err := ReadModulePath(path.Join(modRoot, "go.mod")) + if err != nil { + return "", err + } + relDir, err := filepath.Rel(modRoot, absPath) + if err != nil { + return "", err + } + return filepath.Join(modPath, relDir), nil +} + +// FindGoModuleRoot looks for enclosing Go module. +func FindGoModuleRoot(dir string) (root string) { + dir = filepath.Clean(dir) + for { + if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { + return dir + } + d := filepath.Dir(dir) + if d == dir { + break + } + dir = d + } + return "" +} + +var modulePathRE = regexp.MustCompile(`module[ \t]+([^ \t\r\n]+)`) + +// ReadModulePath reads module path from go.mod file. +func ReadModulePath(gomod string) (string, error) { + data, err := os.ReadFile(gomod) + if err != nil { + return "", err + } + m := modulePathRE.FindSubmatch(data) + if m == nil { + return "", err + } + return string(m[1]), nil +} + +// SplitAndStrip takes a string and splits it any separator +func SplitAndStrip(s string) []string { + return strings.FieldsFunc(s, func(c rune) bool { + return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_' && c != '.' && c != '/' + }) +} + +func ExpandPaths(paths ...string) string { + if strings.HasPrefix(paths[0], "~/") { + dirname, _ := os.UserHomeDir() + paths[0] = filepath.Join(dirname, paths[0][2:]) + } + return os.ExpandEnv(filepath.Join(paths...)) +} + +func getGoModuleFromPath(path string) string { + cmd := exec.Command("go", "list") + cmd.Dir = path + cmd.Stderr = os.Stderr + output, err := cmd.Output() + if err != nil { + logrus.Debugf("Failed to 'go list' : %s", err) + return "" + } + soutput := strings.TrimSpace(string(output)) + if soutput == "" { + logrus.Debugf("'go list' did not return anything") + return "" + } + return soutput +} + +func GetTargetPackagePath(userPackageName string, outputPath string) string { + if userPackageName != "" { + return userPackageName + } + modPath := getGoModuleFromPath(outputPath) + if modPath != "" { + return modPath + } else if filepath.IsAbs(outputPath) { + logrus.Fatalf("Failed find go module name for %s", outputPath) + } + modPath = getGoModuleFromPath(".") + if modPath == "" { + logrus.Fatalf("Failed find relative go module name for %s", outputPath) + } + + return filepath.Join(modPath, outputPath) +} + +func GetApiFileDirectory(apiSrcDir, vppSrcDir string) string { + if apiSrcDir != "" { + return ExpandPaths(apiSrcDir) + } else if vppSrcDir != "" { + // Get directory containing the binAPI package + cmd := exec.Command("make", "json-api-files") + cmd.Dir = ExpandPaths(vppSrcDir) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + logrus.Fatalf("Failed to 'make json-api-files' : %s", err) + return "" + } + return ExpandPaths(vppSrcDir, generatedJsonPath) + } else { + return vppapi.DefaultDir + } +} diff --git a/binapigen/vppapi/integration_test.go b/binapigen/vppapi/integration_test.go index b4d1ee71..e6a57aab 100644 --- a/binapigen/vppapi/integration_test.go +++ b/binapigen/vppapi/integration_test.go @@ -25,7 +25,7 @@ import ( ) func TestParse(t *testing.T) { - files, err := vppapi.Parse() + files, err := vppapi.ParseDir(vppapi.DefaultDir) if err != nil { t.Fatal(err) } diff --git a/binapigen/vppapi/vppapi.go b/binapigen/vppapi/vppapi.go index 91bcba0e..ecf1905c 100644 --- a/binapigen/vppapi/vppapi.go +++ b/binapigen/vppapi/vppapi.go @@ -29,8 +29,8 @@ const ( APIFileExtension = ".api.json" ) -// FindFiles finds API files located in dir or in a nested directory that is not nested deeper than deep. -func FindFiles(dir string, deep int) (files []string, err error) { +// findFiles finds API files located in dir or in a nested directory that is not nested deeper than deep. +func findFiles(dir string, deep int) (files []string, err error) { entries, err := os.ReadDir(dir) if err != nil { return nil, fmt.Errorf("reading directory %s failed: %v", dir, err) @@ -38,7 +38,7 @@ func FindFiles(dir string, deep int) (files []string, err error) { for _, e := range entries { if e.IsDir() && deep > 0 { nestedDir := filepath.Join(dir, e.Name()) - if nested, err := FindFiles(nestedDir, deep-1); err != nil { + if nested, err := findFiles(nestedDir, deep-1); err != nil { return nil, err } else { files = append(files, nested...) @@ -50,24 +50,26 @@ func FindFiles(dir string, deep int) (files []string, err error) { return files, nil } -// Parse parses API files in directory DefaultDir. -func Parse() ([]*File, error) { - return ParseDir(DefaultDir) -} - // ParseDir finds and parses API files in given directory and returns parsed files. // Supports API files in JSON format (.api.json) only. func ParseDir(apiDir string) ([]*File, error) { - list, err := FindFiles(apiDir, 1) - if err != nil { - return nil, err + info, err := os.Stat(apiDir) + if os.IsNotExist(err) { + return nil, fmt.Errorf("%s does not exist.", apiDir) + } + list := []string{apiDir} // in tests we pass a single file name + if info.IsDir() { + list, err = findFiles(apiDir, 1) + if err != nil { + return nil, err + } } logf("found %d files in API dir %q", len(list), apiDir) var files []*File for _, file := range list { - module, err := ParseFile(file) + module, err := parseFile(file) if err != nil { return nil, err } @@ -76,8 +78,8 @@ func ParseDir(apiDir string) ([]*File, error) { return files, nil } -// ParseFile parses API file and returns File. -func ParseFile(apiFile string) (*File, error) { +// parseFile parses API file and returns File. +func parseFile(apiFile string) (*File, error) { if !strings.HasSuffix(apiFile, APIFileExtension) { return nil, fmt.Errorf("unsupported file format: %q", apiFile) } @@ -92,7 +94,7 @@ func ParseFile(apiFile string) (*File, error) { logf("parsing file %q", base) - module, err := ParseRaw(data) + module, err := parseRaw(data) if err != nil { return nil, fmt.Errorf("parsing file %s failed: %v", base, err) } @@ -102,8 +104,8 @@ func ParseFile(apiFile string) (*File, error) { return module, nil } -// ParseRaw parses raw API file data and returns File. -func ParseRaw(data []byte) (file *File, err error) { +// parseRaw parses raw API file data and returns File. +func parseRaw(data []byte) (file *File, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic occurred: %v", e) diff --git a/binapigen/vppapi/vppapi_test.go b/binapigen/vppapi/vppapi_test.go index 0a65678a..d1ae8594 100644 --- a/binapigen/vppapi/vppapi_test.go +++ b/binapigen/vppapi/vppapi_test.go @@ -25,7 +25,7 @@ import ( func TestGetInputFiles(t *testing.T) { RegisterTestingT(t) - result, err := FindFiles("testdata", 1) + result, err := findFiles("testdata", 1) Expect(err).ShouldNot(HaveOccurred()) Expect(result).To(HaveLen(6)) for _, file := range result { @@ -36,7 +36,7 @@ func TestGetInputFiles(t *testing.T) { func TestGetInputFilesError(t *testing.T) { RegisterTestingT(t) - result, err := FindFiles("nonexisting_directory", 1) + result, err := findFiles("nonexisting_directory", 1) Expect(err).Should(HaveOccurred()) Expect(result).To(BeNil()) } @@ -46,7 +46,7 @@ func TestReadJson(t *testing.T) { inputData, err := os.ReadFile("testdata/af_packet.api.json") Expect(err).ShouldNot(HaveOccurred()) - result, err := ParseRaw(inputData) + result, err := parseRaw(inputData) Expect(err).ShouldNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.EnumTypes).To(HaveLen(0)) @@ -60,13 +60,13 @@ func TestReadJsonError(t *testing.T) { inputData, err := os.ReadFile("testdata/input-read-json-error.json") Expect(err).ShouldNot(HaveOccurred()) - result, err := ParseRaw(inputData) + result, err := parseRaw(inputData) Expect(err).Should(HaveOccurred()) Expect(result).To(BeNil()) } func TestParseFile(t *testing.T) { - module, err := ParseFile("testdata/vpe.api.json") + module, err := parseFile("testdata/vpe.api.json") if err != nil { t.Fatal("unexpected error:", err) } @@ -111,7 +111,7 @@ func TestParseFile(t *testing.T) { } func TestParseFileUnsupported(t *testing.T) { - _, err := ParseFile("testdata/input.txt") + _, err := parseFile("testdata/input.txt") if err == nil { t.Fatal("expected error") } diff --git a/cmd/binapi-generator/main.go b/cmd/binapi-generator/main.go index 3ccc138f..eb2785f1 100644 --- a/cmd/binapi-generator/main.go +++ b/cmd/binapi-generator/main.go @@ -18,53 +18,65 @@ import ( "flag" "fmt" "os" - "path/filepath" - "strings" - "unicode" + "github.com/fatih/color" "github.com/sirupsen/logrus" "go.fd.io/govpp/binapigen" + _ "go.fd.io/govpp/binapigen/plugins" "go.fd.io/govpp/binapigen/vppapi" "go.fd.io/govpp/version" ) func init() { flag.Usage = func() { - fmt.Fprintf(os.Stderr, "USAGE\n") - fmt.Fprintf(os.Stderr, " Parse API_FILES and generate Go bindings\n") - fmt.Fprintf(os.Stderr, " Provide API_FILES by file name, or with full path including extension.\n") - fmt.Fprintf(os.Stderr, " %s [OPTION] API_FILES\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Generating Go bindings for the VPP API\n") + fmt.Fprintf(os.Stderr, "--------------------------------------\n\n") + fmt.Fprintf(os.Stderr, "This generates VPP api Go bindings based on .api.json files or a VPP repository\n\n") + fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "OPTIONS\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nEXAMPLES:\n") - fmt.Fprintf(os.Stderr, " %s \\\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " --input-dir=$VPP/build-root/install-vpp-native/vpp/share/vpp/api/ \\\n") - fmt.Fprintf(os.Stderr, " --output-dir=~/output \\\n") - fmt.Fprintf(os.Stderr, " interface ip\n") - fmt.Fprintf(os.Stderr, " Assuming --input-dir contains interface.api.json & ip.api.json\n") + fmt.Fprintf(os.Stderr, " Generate bindings from VPP API files (*.api.json):\n") + fmt.Fprintf(os.Stderr, " %s --api /usr/share/vpp/api --output ./myapp/binding --filter interface,ip\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " Generate bindings from the VPP repository :\n") + fmt.Fprintf(os.Stderr, " %s --vpp ~/vpp --output ./myapp/binding\n", os.Args[0]) } } func printErrorAndExit(msg string) { - fmt.Fprintf(os.Stderr, "Error: %s\n\n", msg) + color.New(color.FgRed).Fprintf(os.Stderr, "Error:\n%s\n\n", msg) flag.Usage() os.Exit(1) } func main() { - var ( - theApiDir = flag.String("input-dir", vppapi.DefaultDir, "Input directory containing API files. (e.g. )") - theOutputDir = flag.String("output-dir", "binapi", "Output directory where code will be generated.") - importPrefix = flag.String("import-prefix", "", "Prefix imports in the generated go code. \nE.g. other API Files (e.g. api_file.ba.go) will be imported with :\nimport (\n api_file \"/api_file\"\n)") - generatorPlugins = flag.String("gen", "rpc", "List of generator plugins to run for files.") - theInputFile = flag.String("input-file", "", "DEPRECATED: Use program arguments to define files to generate.") - - printVersion = flag.Bool("version", false, "Prints version and exits.") - debugLog = flag.Bool("debug", false, "Enable verbose logging.") - noVersionInfo = flag.Bool("no-version-info", false, "Disable version info in generated files.") - noSourcePathInfo = flag.Bool("no-source-path-info", false, "Disable source path info in generated files.") - ) + apiSrcDir := flag.String("input-dir", vppapi.DefaultDir, "[DEPRECATED, use --api] Input directory containing API files.") + flag.StringVar(apiSrcDir, "api", "", "Generate based on .api files in this directory.") + vppSrcDir := flag.String("vpp", "", "Generate based on a vpp cloned in this directory.") + + // Filtering API files + filterList := flag.String("input-file", "", "[DEPRECATED: Use --filter] defines apis to generate.") + flag.StringVar(filterList, "filter", "", "Comma separated list of api to generate (e.g. ipip,ipsec, ...)") + + // Where to output the files + outputDir := flag.String("output-dir", ".", "[DEPRECATED, use --output] Output directory where code will be generated.") + flag.StringVar(outputDir, "output", "", "Output directory for generated files.") + flag.StringVar(outputDir, "o", "", "Output directory for generated files.") + + // Package name to use + importPrefix := flag.String("import-prefix", "", "[DEPRECATED, use --package] Prefix imports in the generated go code.") + flag.StringVar(importPrefix, "package", "", "Package path to generate to e.g. myapp.me.com/myapp/bindings. If omitted, we'll try to guess it from go modules and the output directory") + + // Plugins + generatorPlugins := flag.String("gen", "", "[DEPRECATED, use --plugins] List of generator plugins to run for files.") + flag.StringVar(generatorPlugins, "plugins", "", fmt.Sprintf("List of generator plugins to run for files. (%s or a path to an external plugin)", binapigen.GetAvailablePluginNames())) + + printVersion := flag.Bool("version", false, "Prints version and exits.") + debugLog := flag.Bool("debug", false, "Enable verbose logging.") + noVersionInfo := flag.Bool("no-version-info", false, "Disable version info in generated files.") + noSourcePathInfo := flag.Bool("no-source-path-info", false, "Disable source path info in generated files.") + flag.Parse() if *printVersion { @@ -76,44 +88,31 @@ func main() { logrus.SetLevel(logrus.DebugLevel) } - var filesToGenerate []string - if *theInputFile != "" { - if flag.NArg() > 0 { - printErrorAndExit("input-file cannot be combined with files to generate in arguments") - } - filesToGenerate = append(filesToGenerate, *theInputFile) - } else { - filesToGenerate = append(filesToGenerate, flag.Args()...) + if *vppSrcDir == "" && *apiSrcDir == "" { + printErrorAndExit("Please provide either --api or --vpp") } - opts := binapigen.Options{ - ImportPrefix: *importPrefix, - OutputDir: *theOutputDir, - NoVersionInfo: *noVersionInfo, - NoSourcePathInfo: *noSourcePathInfo, + fileFilter := binapigen.SplitAndStrip(*filterList) + if flag.NArg() > 0 { + logrus.Warnf("Deprecated, use --filter instead to pass the API names") + fileFilter = append(fileFilter, flag.Args()...) } - if opts.OutputDir == "binapi" { - if wd, _ := os.Getwd(); filepath.Base(wd) == "binapi" { - opts.OutputDir = "." - } - } - apiDir := *theApiDir - genPlugins := strings.FieldsFunc(*generatorPlugins, func(c rune) bool { - return !unicode.IsLetter(c) && !unicode.IsNumber(c) - }) - binapigen.Run(apiDir, filesToGenerate, opts, func(gen *binapigen.Generator) error { - for _, file := range gen.Files { - if !file.Generate { - continue - } - binapigen.GenerateAPI(gen, file) - for _, p := range genPlugins { - if err := binapigen.RunPlugin(p, gen, file); err != nil { - return err - } - } - } - return nil + generator, err := binapigen.New(binapigen.Options{ + ImportPrefix: binapigen.GetTargetPackagePath(*importPrefix, *outputDir), + OutputDir: binapigen.ExpandPaths(*outputDir), + NoVersionInfo: *noVersionInfo, + NoSourcePathInfo: *noSourcePathInfo, + ActivePluginNames: binapigen.SplitAndStrip(*generatorPlugins), + ApiDir: binapigen.GetApiFileDirectory(*apiSrcDir, *vppSrcDir), + FileFilter: fileFilter, }) + if err != nil { + logrus.Fatalf("error creating generator %s", err) + } + + err = generator.Generate() + if err != nil { + logrus.Fatalf("error generating %s", err) + } } diff --git a/cmd/govpp/main.go b/cmd/govpp/main.go index 1edf17c8..ae467fd7 100644 --- a/cmd/govpp/main.go +++ b/cmd/govpp/main.go @@ -38,7 +38,7 @@ import ( func main() { flag.Parse() - apifiles, err := vppapi.Parse() + apifiles, err := vppapi.ParseDir(vppapi.DefaultDir) if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index d0931898..04116331 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,23 @@ go 1.18 require ( github.com/bennyscetbun/jsongo v1.1.0 + github.com/fatih/color v1.13.0 github.com/fsnotify/fsnotify v1.4.9 github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe github.com/onsi/gomega v1.19.0 github.com/pkg/profile v1.2.1 github.com/sirupsen/logrus v1.6.0 + golang.org/x/text v0.3.7 ) require ( github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - golang.org/x/text v0.3.7 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index b4028b32..ea50daf7 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/bennyscetbun/jsongo v1.1.0 h1:ZDSks3aLP13jhY139lWaUqZaU8G0tELMohzumut github.com/bennyscetbun/jsongo v1.1.0/go.mod h1:suxbVmjBV8+A2BBAM5EYVh6Uj8j3rqJhzWf3hv7Ff8U= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60= @@ -15,6 +17,11 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y= github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= @@ -30,6 +37,9 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=