X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=binapigen%2Fgenerate.go;h=d35427ff1c0a9331f2b4527d8bbd785a719296b4;hb=c7ae74a95d1bd6fefcbb061f5f045c60c11e32fc;hp=1f9b89a0616a42b29f62971be1d91c6267ae8ac8;hpb=94620e85f0bdbb054af07ce3670fadc1f76cfdf0;p=govpp.git diff --git a/binapigen/generate.go b/binapigen/generate.go index 1f9b89a..d35427f 100644 --- a/binapigen/generate.go +++ b/binapigen/generate.go @@ -15,16 +15,13 @@ package binapigen import ( - "bytes" "fmt" "io" - "os/exec" - "path" - "path/filepath" "sort" "strings" "git.fd.io/govpp.git/version" + "github.com/sirupsen/logrus" ) // generatedCodeVersion indicates a version of the generated code. @@ -33,7 +30,7 @@ import ( // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion). const generatedCodeVersion = 2 -// message field names +// common message fields const ( msgIdField = "_vl_msg_id" clientIndexField = "client_index" @@ -41,23 +38,16 @@ const ( retvalField = "retval" ) +// global API info const ( - outputFileExt = ".ba.go" // file extension of the Go generated files - rpcFileSuffix = "_rpc" // file name suffix for the RPC services - constModuleName = "ModuleName" // module name constant constAPIVersion = "APIVersion" // API version constant constVersionCrc = "VersionCrc" // version CRC constant +) +// generated fiels +const ( unionDataField = "XXX_UnionData" // name for the union data field - - serviceApiName = "RPCService" // name for the RPC service interface - serviceImplName = "serviceClient" // name for the RPC service implementation - serviceClientName = "ServiceClient" // name for the RPC service client - - // TODO: register service descriptor - //serviceDescType = "ServiceDesc" // name for service descriptor type - //serviceDescName = "_ServiceRPC_serviceDesc" // name for service descriptor var ) // MessageType represents the type of a VPP message @@ -70,22 +60,93 @@ const ( otherMessage // other VPP message ) -type GenFile struct { - *Generator - filename string - file *File - packageDir string - buf bytes.Buffer -} - -func generatePackage(ctx *GenFile, w io.Writer) { +func generateFileBinapi(ctx *GenFile, w io.Writer) { logf("----------------------------") - logf("generating binapi package: %q", ctx.file.PackageName) + logf("generating BINAPI file package: %q", ctx.file.PackageName) logf("----------------------------") - generateHeader(ctx, w) + // generate file header + fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + fmt.Fprintln(w, "// versions:") + fmt.Fprintf(w, "// binapi-generator: %s\n", version.Version()) + if ctx.IncludeVppVersion { + fmt.Fprintf(w, "// VPP: %s\n", ctx.VPPVersion) + } + fmt.Fprintf(w, "// source: %s\n", ctx.file.Path) + fmt.Fprintln(w) + + generatePackageHeader(ctx, w) generateImports(ctx, w) + generateApiInfo(ctx, w) + generateTypes(ctx, w) + generateMessages(ctx, w) + + generateImportRefs(ctx, w) +} + +func generatePackageHeader(ctx *GenFile, w io.Writer) { + fmt.Fprintln(w, "/*") + fmt.Fprintf(w, "Package %s contains generated code for VPP API file %s.api (%s).\n", + ctx.file.PackageName, ctx.file.Name, ctx.file.Version()) + fmt.Fprintln(w) + fmt.Fprintln(w, "It consists of:") + printObjNum := func(obj string, num int) { + if num > 0 { + if num > 1 { + if strings.HasSuffix(obj, "s") { + obj += "es" + } else { + obj += "s" + } + } + fmt.Fprintf(w, "\t%3d %s\n", num, obj) + } + } + printObjNum("alias", len(ctx.file.Aliases)) + printObjNum("enum", len(ctx.file.Enums)) + printObjNum("message", len(ctx.file.Messages)) + printObjNum("type", len(ctx.file.Structs)) + printObjNum("union", len(ctx.file.Unions)) + fmt.Fprintln(w, "*/") + fmt.Fprintf(w, "package %s\n", ctx.file.PackageName) + fmt.Fprintln(w) +} + +func generateImports(ctx *GenFile, w io.Writer) { + fmt.Fprintln(w, "import (") + fmt.Fprintln(w, ` "bytes"`) + fmt.Fprintln(w, ` "context"`) + fmt.Fprintln(w, ` "encoding/binary"`) + fmt.Fprintln(w, ` "fmt"`) + fmt.Fprintln(w, ` "io"`) + fmt.Fprintln(w, ` "math"`) + fmt.Fprintln(w, ` "net"`) + fmt.Fprintln(w, ` "strconv"`) + fmt.Fprintln(w, ` "strings"`) + fmt.Fprintln(w) + fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") + fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec") + fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc") + imports := listImports(ctx) + if len(imports) > 0 { + fmt.Fprintln(w) + for imp, importPath := range imports { + fmt.Fprintf(w, "\t%s \"%s\"\n", imp, importPath) + } + } + fmt.Fprintln(w, ")") + fmt.Fprintln(w) + + fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file") + fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.") + fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the") + fmt.Fprintln(w, "// GoVPP api package needs to be updated.") + fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion) + fmt.Fprintln(w) +} + +func generateApiInfo(ctx *GenFile, w io.Writer) { // generate module desc fmt.Fprintln(w, "const (") fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName) @@ -99,12 +160,16 @@ func generatePackage(ctx *GenFile, w io.Writer) { } fmt.Fprintln(w, ")") fmt.Fprintln(w) +} +func generateTypes(ctx *GenFile, w io.Writer) { // generate enums if len(ctx.file.Enums) > 0 { for _, enum := range ctx.file.Enums { if imp, ok := ctx.file.imports[enum.Name]; ok { - generateImportedAlias(ctx, w, enum.GoName, imp) + if strings.HasSuffix(ctx.file.Name, "_types") { + generateImportedAlias(ctx, w, enum.GoName, imp) + } continue } generateEnum(ctx, w, enum) @@ -115,7 +180,9 @@ func generatePackage(ctx *GenFile, w io.Writer) { if len(ctx.file.Aliases) > 0 { for _, alias := range ctx.file.Aliases { if imp, ok := ctx.file.imports[alias.Name]; ok { - generateImportedAlias(ctx, w, alias.GoName, imp) + if strings.HasSuffix(ctx.file.Name, "_types") { + generateImportedAlias(ctx, w, alias.GoName, imp) + } continue } generateAlias(ctx, w, alias) @@ -126,7 +193,9 @@ func generatePackage(ctx *GenFile, w io.Writer) { if len(ctx.file.Structs) > 0 { for _, typ := range ctx.file.Structs { if imp, ok := ctx.file.imports[typ.Name]; ok { - generateImportedAlias(ctx, w, typ.GoName, imp) + if strings.HasSuffix(ctx.file.Name, "_types") { + generateImportedAlias(ctx, w, typ.GoName, imp) + } continue } generateStruct(ctx, w, typ) @@ -137,135 +206,49 @@ func generatePackage(ctx *GenFile, w io.Writer) { if len(ctx.file.Unions) > 0 { for _, union := range ctx.file.Unions { if imp, ok := ctx.file.imports[union.Name]; ok { - generateImportedAlias(ctx, w, union.GoName, imp) + if strings.HasSuffix(ctx.file.Name, "_types") { + generateImportedAlias(ctx, w, union.GoName, imp) + } continue } generateUnion(ctx, w, union) } } - - // generate messages - if len(ctx.file.Messages) > 0 { - for _, msg := range ctx.file.Messages { - generateMessage(ctx, w, msg) - } - - initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName) - - // generate message registrations - fmt.Fprintf(w, "func init() { %s() }\n", initFnName) - fmt.Fprintf(w, "func %s() {\n", initFnName) - for _, msg := range ctx.file.Messages { - fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", - msg.GoName, ctx.file.Name+"."+msg.GoName) - } - fmt.Fprintln(w, "}") - fmt.Fprintln(w) - - // generate list of messages - fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n") - fmt.Fprintln(w, "func AllMessages() []api.Message {") - fmt.Fprintln(w, "\treturn []api.Message{") - for _, msg := range ctx.file.Messages { - fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName) - } - fmt.Fprintln(w, "}") - fmt.Fprintln(w, "}") - } - - generateFooter(ctx, w) - } -func generateHeader(ctx *GenFile, w io.Writer) { - fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") - fmt.Fprintln(w, "// versions:") - fmt.Fprintf(w, "// binapi-generator: %s\n", version.Version()) - if ctx.IncludeVppVersion { - fmt.Fprintf(w, "// VPP: %s\n", ctx.VPPVersion) +func generateMessages(ctx *GenFile, w io.Writer) { + if len(ctx.file.Messages) == 0 { + return } - fmt.Fprintf(w, "// source: %s\n", ctx.file.Path) - fmt.Fprintln(w) - fmt.Fprintln(w, "/*") - fmt.Fprintf(w, "Package %s contains generated code for VPP binary API defined by %s.api (version %s).\n", - ctx.file.PackageName, ctx.file.Name, ctx.file.Version()) - fmt.Fprintln(w) - fmt.Fprintln(w, "It consists of:") - printObjNum := func(obj string, num int) { - if num > 0 { - if num > 1 { - if strings.HasSuffix(obj, "s") { - - obj += "es" - } else { - obj += "s" - } - } - fmt.Fprintf(w, "\t%3d %s\n", num, obj) - } + for _, msg := range ctx.file.Messages { + generateMessage(ctx, w, msg) } - //printObjNum("RPC", len(ctx.file.Service.RPCs)) - printObjNum("alias", len(ctx.file.Aliases)) - printObjNum("enum", len(ctx.file.Enums)) - printObjNum("message", len(ctx.file.Messages)) - printObjNum("type", len(ctx.file.Structs)) - printObjNum("union", len(ctx.file.Unions)) - fmt.Fprintln(w, "*/") - fmt.Fprintf(w, "package %s\n", ctx.file.PackageName) - fmt.Fprintln(w) -} -func generateImports(ctx *GenFile, w io.Writer) { - fmt.Fprintln(w, "import (") - fmt.Fprintln(w, ` "bytes"`) - fmt.Fprintln(w, ` "context"`) - fmt.Fprintln(w, ` "encoding/binary"`) - fmt.Fprintln(w, ` "io"`) - fmt.Fprintln(w, ` "math"`) - fmt.Fprintln(w, ` "strconv"`) - fmt.Fprintln(w) - fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") - fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec") - fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc") - if len(ctx.file.Imports) > 0 { - fmt.Fprintln(w) - for _, imp := range ctx.file.Imports { - importPath := path.Join(ctx.ImportPrefix, imp) - if ctx.ImportPrefix == "" { - importPath = getImportPath(ctx.packageDir, imp) - } - fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath)) - } - } - fmt.Fprintln(w, ")") - fmt.Fprintln(w) + // generate message registrations + initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName) - fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file") - fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.") - fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the") - fmt.Fprintln(w, "// GoVPP api package needs to be updated.") - fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion) + fmt.Fprintf(w, "func init() { %s() }\n", initFnName) + fmt.Fprintf(w, "func %s() {\n", initFnName) + for _, msg := range ctx.file.Messages { + fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", + msg.GoName, ctx.file.Name+"."+msg.GoName) + } + fmt.Fprintln(w, "}") fmt.Fprintln(w) -} -func getImportPath(outputDir string, pkg string) string { - absPath, err := filepath.Abs(filepath.Join(outputDir, "..", pkg)) - if err != nil { - panic(err) - } - cmd := exec.Command("go", "list", absPath) - var errbuf, outbuf bytes.Buffer - cmd.Stdout = &outbuf - cmd.Stderr = &errbuf - if err := cmd.Run(); err != nil { - fmt.Printf("ERR: %v\n", errbuf.String()) - panic(err) + // generate list of messages + fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n") + fmt.Fprintln(w, "func AllMessages() []api.Message {") + fmt.Fprintln(w, "\treturn []api.Message{") + for _, msg := range ctx.file.Messages { + fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName) } - return outbuf.String() + fmt.Fprintln(w, "}") + fmt.Fprintln(w, "}") } -func generateFooter(ctx *GenFile, w io.Writer) { +func generateImportRefs(ctx *GenFile, w io.Writer) { fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n") fmt.Fprintf(w, "var _ = api.RegisterMessage\n") fmt.Fprintf(w, "var _ = codec.DecodeString\n") @@ -273,9 +256,12 @@ func generateFooter(ctx *GenFile, w io.Writer) { fmt.Fprintf(w, "var _ = context.Background\n") fmt.Fprintf(w, "var _ = io.Copy\n") fmt.Fprintf(w, "var _ = strconv.Itoa\n") + fmt.Fprintf(w, "var _ = strings.Contains\n") fmt.Fprintf(w, "var _ = struc.Pack\n") fmt.Fprintf(w, "var _ = binary.BigEndian\n") fmt.Fprintf(w, "var _ = math.Float32bits\n") + fmt.Fprintf(w, "var _ = net.ParseIP\n") + fmt.Fprintf(w, "var _ = fmt.Errorf\n") } func generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) { @@ -353,6 +339,13 @@ func generateAlias(ctx *GenFile, w io.Writer, alias *Alias) { dataType := convertToGoType(ctx.file, alias.Type) fmt.Fprintf(w, "%s\n", dataType) + // generate alias-specific methods + switch alias.Name { + case "mac_address": + fmt.Fprintln(w) + generateMacAddressConversion(w, name) + } + fmt.Fprintln(w) } @@ -384,6 +377,16 @@ func generateStruct(ctx *GenFile, w io.Writer, typ *Struct) { // generate name getter generateTypeNameGetter(w, name, typ.Name) + // generate type-specific methods + switch typ.Name { + case "address": + fmt.Fprintln(w) + generateIPAddressConversion(w, name) + case "prefix": + fmt.Fprintln(w) + generatePrefixConversion(w, name) + } + fmt.Fprintln(w) } @@ -522,7 +525,7 @@ func generateMessage(ctx *GenFile, w io.Writer, msg *Message) { // skip internal fields switch strings.ToLower(field.Name) { - case /*crcField,*/ msgIdField: + case msgIdField: continue case clientIndexField, contextField: if n == 0 { @@ -590,20 +593,22 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field } lvl := 0 - var encodeFields func(fields []*Field, parentName string) - encodeFields = func(fields []*Field, parentName string) { + var sizeFields func(fields []*Field, parentName string) + sizeFields = func(fields []*Field, parentName string) { lvl++ defer func() { lvl-- }() n := 0 for _, field := range fields { - // skip internal fields - switch strings.ToLower(field.Name) { - case /*crcField,*/ msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { + if field.ParentMessage != nil { + // skip internal fields + switch strings.ToLower(field.Name) { + case msgIdField: continue + case clientIndexField, contextField: + if n == 0 { + continue + } } } n++ @@ -646,12 +651,12 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { if encodeBaseType(alias.Type, name, alias.Length, "") { } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { - encodeFields(typ.Fields, name) + sizeFields(typ.Fields, name) } else { fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type) } } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { - encodeFields(typ.Fields, name) + sizeFields(typ.Fields, name) } else if union := getUnionByRef(ctx.file, field.Type); union != nil { maxSize := getUnionSize(ctx.file, union) fmt.Fprintf(w, "\tsize += %d\n", maxSize) @@ -665,7 +670,7 @@ func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field } } - encodeFields(fields, "m") + sizeFields(fields, "m") fmt.Fprintf(w, "return size\n") @@ -786,13 +791,15 @@ func generateMessageMarshal(ctx *GenFile, w io.Writer, name string, fields []*Fi n := 0 for _, field := range fields { - // skip internal fields - switch strings.ToLower(field.Name) { - case /*crcField,*/ msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { + if field.ParentMessage != nil { + // skip internal fields + switch strings.ToLower(field.Name) { + case msgIdField: continue + case clientIndexField, contextField: + if n == 0 { + continue + } } } n++ @@ -1004,13 +1011,15 @@ func generateMessageUnmarshal(ctx *GenFile, w io.Writer, name string, fields []* n := 0 for _, field := range fields { - // skip internal fields - switch strings.ToLower(field.Name) { - case /*crcField,*/ msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { + if field.ParentMessage != nil { + // skip internal fields + switch strings.ToLower(field.Name) { + case msgIdField: continue + case clientIndexField, contextField: + if n == 0 { + continue + } } } n++ @@ -1220,6 +1229,105 @@ func generateTypeNameGetter(w io.Writer, structName, msgName string) { fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName) } +func generateIPAddressConversion(w io.Writer, structName string) { + f1 := func(ipVer, ipVerExt int) string { + return fmt.Sprintf(`address.Af = ADDRESS_IP%[1]d + var ip%[1]daddr IP%[1]dAddress + copy(ip%[1]daddr[:], netIP.To%[2]d()) + address.Un.SetIP%[1]d(ip%[1]daddr)`, ipVer, ipVerExt) + } + f2 := func(ipVer, ipVerExt int) string { + return fmt.Sprintf("ip%[1]dAddress := a.Un.GetIP%[1]d()\nip = net.IP(ip%[1]dAddress[:]).To%[2]d().String()", + ipVer, ipVerExt) + } + // IP to Address + fmt.Fprintf(w, `func ParseAddress(ip string) (%[1]s, error) { + var address %[1]s + netIP := net.ParseIP(ip) + if netIP == nil { + return address, fmt.Errorf("invalid address: %[2]s", ip) + } + if ip4 := netIP.To4(); ip4 == nil { + %[3]s + } else { + %[4]s + } + return address, nil +} +`, structName, "%s", f1(6, 16), f1(4, 4)) + fmt.Fprintln(w) + + // Address to IP + fmt.Fprintln(w) + fmt.Fprintf(w, `func (a *%[1]s) ToString() string { + var ip string + if a.Af == ADDRESS_IP6 { + %[2]s + } else { + %[3]s + } + return ip +}`, structName, f2(6, 16), f2(4, 4)) +} + +func generatePrefixConversion(w io.Writer, structName string) { + fErr := func() string { + return fmt.Sprintf(`if err != nil { + return Prefix{}, fmt.Errorf("invalid IP %s: %s", ip, err) + }`, "%s", "%v") + } + + // IP to Prefix + fmt.Fprintf(w, `func ParsePrefix(ip string) (prefix %[1]s, err error) { + hasPrefix := strings.Contains(ip, "/") + if hasPrefix { + netIP, network, err := net.ParseCIDR(ip) + %[2]s + maskSize, _ := network.Mask.Size() + prefix.Len = byte(maskSize) + prefix.Address, err = ParseAddress(netIP.String()) + %[2]s + } else { + netIP := net.ParseIP(ip) + defaultMaskSize, _ := net.CIDRMask(32, 32).Size() + if netIP.To4() == nil { + defaultMaskSize, _ = net.CIDRMask(128, 128).Size() + } + prefix.Len = byte(defaultMaskSize) + prefix.Address, err = ParseAddress(netIP.String()) + %[2]s + } + return prefix, nil +}`, structName, fErr(), nil) + fmt.Fprintln(w) + + // Prefix to IP + fmt.Fprintln(w) + fmt.Fprintf(w, `func (p *%[1]s) ToString() string { + ip := p.Address.ToString() + return ip + "/" + strconv.Itoa(int(p.Len)) + }`, structName) +} + +func generateMacAddressConversion(w io.Writer, structName string) { + // string to MAC + fmt.Fprintf(w, `func ParseMAC(mac string) (parsed %[1]s, err error) { + var hw net.HardwareAddr + if hw, err = net.ParseMAC(mac); err != nil { + return + } + copy(parsed[:], hw[:]) + return +}`, structName) + fmt.Fprintln(w) + + // MAC to string + fmt.Fprintln(w) + fmt.Fprintf(w, `func (m *%[1]s) ToString() string { + return net.HardwareAddr(m[:]).String() + }`, structName) +} + func generateCrcGetter(w io.Writer, structName, crc string) { crc = strings.TrimPrefix(crc, "0x") fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc) @@ -1239,3 +1347,7 @@ func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageTy fmt.Fprintln(w, "}") fmt.Fprintln(w) } + +func logf(f string, v ...interface{}) { + logrus.Debugf(f, v...) +}