aboutsummaryrefslogtreecommitdiffstats
path: root/binapigen/vppapi
diff options
context:
space:
mode:
authorOndrej Fabry <ofabry@cisco.com>2020-07-17 10:36:28 +0200
committerOndrej Fabry <ofabry@cisco.com>2020-07-17 11:43:41 +0200
commitd1f24d37bd447b64e402298bb8eb2479681facf9 (patch)
treea3fc21ba730a91d8a402c7a5bf9c614e3677c4fc /binapigen/vppapi
parent1548c7e12531e3d055567d761c580a1c7ff0ac40 (diff)
Improve binapi generator
- simplified Size/Marshal/Unmarshal methods - replace struc in unions with custom marshal/unmarshal - fix imports in generated files - fix mock adapter - generate rpc service using low-level stream API (dumps generate control ping or stream msg..) - move examples/binapi to binapi and generate all API for latest release - add binapigen.Plugin for developing custom generator plugins - optionally generate HTTP handlers (REST API) for RPC services - add govpp program for browsing VPP API Change-Id: I092e9ed2b0c17972b3476463c3d4b14dd76ed42b Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
Diffstat (limited to 'binapigen/vppapi')
-rw-r--r--binapigen/vppapi/api.go94
-rw-r--r--binapigen/vppapi/api_schema.go89
-rw-r--r--binapigen/vppapi/parse_json.go210
-rw-r--r--binapigen/vppapi/util.go112
-rw-r--r--binapigen/vppapi/vppapi.go (renamed from binapigen/vppapi/parser.go)49
-rw-r--r--binapigen/vppapi/vppapi_test.go (renamed from binapigen/vppapi/parser_test.go)22
6 files changed, 314 insertions, 262 deletions
diff --git a/binapigen/vppapi/api.go b/binapigen/vppapi/api.go
deleted file mode 100644
index 06d9046..0000000
--- a/binapigen/vppapi/api.go
+++ /dev/null
@@ -1,94 +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 vppapi
-
-type File struct {
- Name string
- Path string
-
- CRC string
- Options map[string]string `json:",omitempty"`
-
- Imports []string `json:",omitempty"`
-
- AliasTypes []AliasType `json:",omitempty"`
- EnumTypes []EnumType `json:",omitempty"`
- StructTypes []StructType `json:",omitempty"`
- UnionTypes []UnionType `json:",omitempty"`
- Messages []Message `json:",omitempty"`
- Service *Service `json:",omitempty"`
-}
-
-func (x File) Version() string {
- if x.Options != nil {
- return x.Options[fileOptionVersion]
- }
- return ""
-}
-
-type AliasType struct {
- Name string
- Type string
- Length int `json:",omitempty"`
-}
-
-type EnumType struct {
- Name string
- Type string
- Entries []EnumEntry
-}
-
-type EnumEntry struct {
- Name string
- Value uint32
-}
-
-type StructType struct {
- Name string
- Fields []Field
-}
-
-type UnionType struct {
- Name string
- Fields []Field
-}
-
-type Message struct {
- Name string
- Fields []Field
- CRC string
-}
-
-type Field struct {
- Name string
- Type string
- Length int `json:",omitempty"`
- Array bool `json:",omitempty"`
- SizeFrom string `json:",omitempty"`
- Meta map[string]interface{} `json:",omitempty"`
-}
-
-type Service struct {
- RPCs []RPC `json:",omitempty"`
-}
-
-type RPC struct {
- Name string
- RequestMsg string
- ReplyMsg string
- Stream bool `json:",omitempty"`
- StreamMsg string `json:",omitempty"`
- Events []string `json:",omitempty"`
-}
diff --git a/binapigen/vppapi/api_schema.go b/binapigen/vppapi/api_schema.go
new file mode 100644
index 0000000..7eceab3
--- /dev/null
+++ b/binapigen/vppapi/api_schema.go
@@ -0,0 +1,89 @@
+// 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 vppapi parses VPP API files without any additional processing.
+package vppapi
+
+type (
+ File struct {
+ Name string
+ Path string
+ CRC string
+
+ Options map[string]string `json:",omitempty"`
+ Imports []string `json:",omitempty"`
+
+ AliasTypes []AliasType `json:",omitempty"`
+ EnumTypes []EnumType `json:",omitempty"`
+ StructTypes []StructType `json:",omitempty"`
+ UnionTypes []UnionType `json:",omitempty"`
+
+ Messages []Message `json:",omitempty"`
+ Service *Service `json:",omitempty"`
+ }
+
+ AliasType struct {
+ Name string
+ Type string
+ Length int `json:",omitempty"`
+ }
+
+ EnumType struct {
+ Name string
+ Type string
+ Entries []EnumEntry
+ }
+
+ EnumEntry struct {
+ Name string
+ Value uint32
+ }
+
+ StructType struct {
+ Name string
+ Fields []Field
+ }
+
+ UnionType struct {
+ Name string
+ Fields []Field
+ }
+
+ Message struct {
+ Name string
+ Fields []Field
+ CRC string
+ }
+
+ Field struct {
+ Name string
+ Type string
+ Length int `json:",omitempty"`
+ Array bool `json:",omitempty"`
+ SizeFrom string `json:",omitempty"`
+ Meta map[string]interface{} `json:",omitempty"`
+ }
+
+ Service struct {
+ RPCs []RPC `json:",omitempty"`
+ }
+
+ RPC struct {
+ Request string
+ Reply string
+ Stream bool `json:",omitempty"`
+ StreamMsg string `json:",omitempty"`
+ Events []string `json:",omitempty"`
+ }
+)
diff --git a/binapigen/vppapi/parse_json.go b/binapigen/vppapi/parse_json.go
index 45b5796..d14865c 100644
--- a/binapigen/vppapi/parse_json.go
+++ b/binapigen/vppapi/parse_json.go
@@ -18,79 +18,52 @@ import (
"encoding/json"
"errors"
"fmt"
+ "log"
"os"
"strings"
"github.com/bennyscetbun/jsongo"
- "github.com/sirupsen/logrus"
)
-var Logger *logrus.Logger
-
-func init() {
- if strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser") {
- Logger = logrus.StandardLogger()
- }
-}
+var debug = strings.Contains(os.Getenv("DEBUG_GOVPP"), "parser")
func logf(f string, v ...interface{}) {
- if Logger != nil {
- Logger.Debugf(f, v...)
+ if debug {
+ log.Printf(f, v...)
}
}
const (
- // file
- objAPIVersion = "vl_api_version"
- objTypes = "types"
- objMessages = "messages"
- objUnions = "unions"
- objEnums = "enums"
- objServices = "services"
- objAliases = "aliases"
- objOptions = "options"
- objImports = "imports"
-
- // message
- messageFieldCrc = "crc"
-
- // alias
- aliasFieldLength = "length"
- aliasFieldType = "type"
-
- // service
- serviceFieldReply = "reply"
- serviceFieldStream = "stream"
- serviceFieldStreamMsg = "stream_msg"
- serviceFieldEvents = "events"
-)
-
-const (
- // file
- fileOptionVersion = "version"
-
- // field
- fieldOptionLimit = "limit"
- fieldOptionDefault = "default"
-
+ // root keys
+ fileAPIVersion = "vl_api_version"
+ fileOptions = "options"
+ fileTypes = "types"
+ fileMessages = "messages"
+ fileUnions = "unions"
+ fileEnums = "enums"
+ fileAliases = "aliases"
+ fileServices = "services"
+ fileImports = "imports"
+ // type keys
+ messageCrc = "crc"
+ enumType = "enumtype"
+ aliasLength = "length"
+ aliasType = "type"
// service
- serviceReplyNull = "null"
+ serviceReply = "reply"
+ serviceStream = "stream"
+ serviceStreamMsg = "stream_msg"
+ serviceEvents = "events"
)
func parseJSON(data []byte) (module *File, err error) {
- defer func() {
- if e := recover(); e != nil {
- err = fmt.Errorf("recovered panic: %v", e)
- }
- }()
-
- // parse JSON data into objects
+ // parse root
jsonRoot := new(jsongo.Node)
if err := json.Unmarshal(data, jsonRoot); err != nil {
return nil, fmt.Errorf("unmarshalling JSON failed: %v", err)
}
- logf("file contents:")
+ logf("file contains:")
for _, key := range jsonRoot.GetKeys() {
if jsonRoot.At(key).Len() > 0 {
logf(" - %2d %s", jsonRoot.At(key).Len(), key)
@@ -100,38 +73,35 @@ func parseJSON(data []byte) (module *File, err error) {
module = new(File)
// parse CRC
- if crc := jsonRoot.At(objAPIVersion); crc.GetType() == jsongo.TypeValue {
- module.CRC = crc.Get().(string)
+ crc := jsonRoot.At(fileAPIVersion)
+ if crc.GetType() == jsongo.TypeValue {
+ module.CRC = crc.MustGetString()
}
// parse options
- opt := jsonRoot.Map(objOptions)
+ opt := jsonRoot.Map(fileOptions)
if opt.GetType() == jsongo.TypeMap {
- module.Options = make(map[string]string, 0)
+ module.Options = make(map[string]string)
for _, key := range opt.GetKeys() {
- optionsNode := opt.At(key)
optionKey := key.(string)
- optionValue := optionsNode.Get().(string)
- module.Options[optionKey] = optionValue
+ optionVal := opt.At(key).MustGetString()
+ module.Options[optionKey] = optionVal
}
}
// parse imports
- imports := jsonRoot.Map(objImports)
- module.Imports = make([]string, 0)
- imported := make(map[string]struct{})
- for i := 0; i < imports.Len(); i++ {
- importNode := imports.At(i)
- imp, err := parseImport(importNode)
- if err != nil {
- return nil, err
- }
- if _, ok := imported[*imp]; ok {
- logf("duplicate import found: %v", *imp)
+ importsNode := jsonRoot.Map(fileImports)
+ module.Imports = make([]string, 0, importsNode.Len())
+ uniq := make(map[string]struct{})
+ for i := 0; i < importsNode.Len(); i++ {
+ importNode := importsNode.At(i)
+ imp := importNode.MustGetString()
+ if _, ok := uniq[imp]; ok {
+ logf("duplicate import found: %v", imp)
continue
}
- imported[*imp] = struct{}{}
- module.Imports = append(module.Imports, *imp)
+ uniq[imp] = struct{}{}
+ module.Imports = append(module.Imports, imp)
}
// avoid duplicate objects
@@ -146,11 +116,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse enum types
- enumsNode := jsonRoot.Map(objEnums)
+ enumsNode := jsonRoot.Map(fileEnums)
module.EnumTypes = make([]EnumType, 0)
for i := 0; i < enumsNode.Len(); i++ {
- enumNode := enumsNode.At(i)
- enum, err := parseEnum(enumNode)
+ enum, err := parseEnum(enumsNode.At(i))
if err != nil {
return nil, err
}
@@ -161,13 +130,12 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse alias types
- aliasesNode := jsonRoot.Map(objAliases)
+ aliasesNode := jsonRoot.Map(fileAliases)
if aliasesNode.GetType() == jsongo.TypeMap {
module.AliasTypes = make([]AliasType, 0)
for _, key := range aliasesNode.GetKeys() {
- aliasNode := aliasesNode.At(key)
aliasName := key.(string)
- alias, err := parseAlias(aliasName, aliasNode)
+ alias, err := parseAlias(aliasName, aliasesNode.At(key))
if err != nil {
return nil, err
}
@@ -179,11 +147,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse struct types
- typesNode := jsonRoot.Map(objTypes)
+ typesNode := jsonRoot.Map(fileTypes)
module.StructTypes = make([]StructType, 0)
for i := 0; i < typesNode.Len(); i++ {
- typNode := typesNode.At(i)
- structyp, err := parseStruct(typNode)
+ structyp, err := parseStruct(typesNode.At(i))
if err != nil {
return nil, err
}
@@ -194,11 +161,10 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse union types
- unionsNode := jsonRoot.Map(objUnions)
+ unionsNode := jsonRoot.Map(fileUnions)
module.UnionTypes = make([]UnionType, 0)
for i := 0; i < unionsNode.Len(); i++ {
- unionNode := unionsNode.At(i)
- union, err := parseUnion(unionNode)
+ union, err := parseUnion(unionsNode.At(i))
if err != nil {
return nil, err
}
@@ -209,12 +175,11 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse messages
- messagesNode := jsonRoot.Map(objMessages)
+ messagesNode := jsonRoot.Map(fileMessages)
if messagesNode.GetType() == jsongo.TypeArray {
module.Messages = make([]Message, messagesNode.Len())
for i := 0; i < messagesNode.Len(); i++ {
- msgNode := messagesNode.At(i)
- msg, err := parseMessage(msgNode)
+ msg, err := parseMessage(messagesNode.At(i))
if err != nil {
return nil, err
}
@@ -223,15 +188,14 @@ func parseJSON(data []byte) (module *File, err error) {
}
// parse services
- servicesNode := jsonRoot.Map(objServices)
+ servicesNode := jsonRoot.Map(fileServices)
if servicesNode.GetType() == jsongo.TypeMap {
module.Service = &Service{
RPCs: make([]RPC, servicesNode.Len()),
}
for i, key := range servicesNode.GetKeys() {
- rpcNode := servicesNode.At(key)
rpcName := key.(string)
- svc, err := parseServiceRPC(rpcName, rpcNode)
+ svc, err := parseServiceRPC(rpcName, servicesNode.At(key))
if err != nil {
return nil, err
}
@@ -242,20 +206,6 @@ func parseJSON(data []byte) (module *File, err error) {
return module, nil
}
-// parseImport parses VPP binary API import from JSON node
-func parseImport(importNode *jsongo.Node) (*string, error) {
- if importNode.GetType() != jsongo.TypeValue {
- return nil, errors.New("invalid JSON for import specified")
- }
-
- importName, ok := importNode.Get().(string)
- if !ok {
- return nil, fmt.Errorf("import name is %T, not a string", importNode.Get())
- }
-
- return &importName, nil
-}
-
// parseEnum parses VPP binary API enum object from JSON node
func parseEnum(enumNode *jsongo.Node) (*EnumType, error) {
if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
@@ -266,7 +216,7 @@ func parseEnum(enumNode *jsongo.Node) (*EnumType, error) {
if !ok {
return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
}
- enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
+ enumType, ok := enumNode.At(enumNode.Len() - 1).At(enumType).Get().(string)
if !ok {
return nil, fmt.Errorf("enum type invalid or missing")
}
@@ -367,7 +317,7 @@ func parseStruct(typeNode *jsongo.Node) (*StructType, error) {
// parseAlias parses VPP binary API alias object from JSON node
func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
- if aliasNode.Len() == 0 || aliasNode.At(aliasFieldType).GetType() != jsongo.TypeValue {
+ if aliasNode.Len() == 0 || aliasNode.At(aliasType).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for alias specified")
}
@@ -375,7 +325,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
Name: aliasName,
}
- if typeNode := aliasNode.At(aliasFieldType); typeNode.GetType() == jsongo.TypeValue {
+ if typeNode := aliasNode.At(aliasType); typeNode.GetType() == jsongo.TypeValue {
typ, ok := typeNode.Get().(string)
if !ok {
return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get())
@@ -385,7 +335,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
}
}
- if lengthNode := aliasNode.At(aliasFieldLength); lengthNode.GetType() == jsongo.TypeValue {
+ if lengthNode := aliasNode.At(aliasLength); lengthNode.GetType() == jsongo.TypeValue {
length, ok := lengthNode.Get().(float64)
if !ok {
return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get())
@@ -398,7 +348,7 @@ func parseAlias(aliasName string, aliasNode *jsongo.Node) (*AliasType, error) {
// parseMessage parses VPP binary API message object from JSON node
func parseMessage(msgNode *jsongo.Node) (*Message, error) {
- if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
+ if msgNode.Len() < 2 || msgNode.At(0).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for message specified")
}
@@ -406,9 +356,8 @@ func parseMessage(msgNode *jsongo.Node) (*Message, error) {
if !ok {
return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
}
- msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageFieldCrc).Get().(string)
+ msgCRC, ok := msgNode.At(msgNode.Len() - 1).At(messageCrc).Get().(string)
if !ok {
-
return nil, fmt.Errorf("message crc invalid or missing")
}
@@ -466,26 +415,16 @@ func parseField(field *jsongo.Node) (*Field, error) {
case jsongo.TypeMap:
fieldMeta := field.At(2)
-
+ if fieldMeta.Len() == 0 {
+ break
+ }
+ f.Meta = map[string]interface{}{}
for _, key := range fieldMeta.GetKeys() {
- metaNode := fieldMeta.At(key)
metaName := key.(string)
- metaValue := metaNode.Get()
-
- switch metaName {
- case fieldOptionLimit:
- metaValue = int(metaNode.Get().(float64))
- case fieldOptionDefault:
- metaValue = metaNode.Get()
- default:
- logrus.Warnf("unknown meta info (%s=%v) for field (%s)", metaName, metaValue, fieldName)
- }
-
- if f.Meta == nil {
- f.Meta = map[string]interface{}{}
- }
+ metaValue := fieldMeta.At(key).Get()
f.Meta[metaName] = metaValue
}
+
default:
return nil, errors.New("invalid JSON for field specified")
}
@@ -503,27 +442,24 @@ func parseField(field *jsongo.Node) (*Field, error) {
// parseServiceRPC parses VPP binary API service object from JSON node
func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
- if rpcNode.Len() == 0 || rpcNode.At(serviceFieldReply).GetType() != jsongo.TypeValue {
+ if rpcNode.Len() == 0 || rpcNode.At(serviceReply).GetType() != jsongo.TypeValue {
return nil, errors.New("invalid JSON for service RPC specified")
}
rpc := RPC{
- Name: rpcName,
- RequestMsg: rpcName,
+ Request: rpcName,
}
- if replyNode := rpcNode.At(serviceFieldReply); replyNode.GetType() == jsongo.TypeValue {
+ if replyNode := rpcNode.At(serviceReply); replyNode.GetType() == jsongo.TypeValue {
reply, ok := replyNode.Get().(string)
if !ok {
return nil, fmt.Errorf("service RPC reply is %T, not a string", replyNode.Get())
}
- if reply != serviceReplyNull {
- rpc.ReplyMsg = reply
- }
+ rpc.Reply = reply
}
// is stream (dump)
- if streamNode := rpcNode.At(serviceFieldStream); streamNode.GetType() == jsongo.TypeValue {
+ if streamNode := rpcNode.At(serviceStream); streamNode.GetType() == jsongo.TypeValue {
var ok bool
rpc.Stream, ok = streamNode.Get().(bool)
if !ok {
@@ -532,7 +468,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
}
// stream message
- if streamMsgNode := rpcNode.At(serviceFieldStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
+ if streamMsgNode := rpcNode.At(serviceStreamMsg); streamMsgNode.GetType() == jsongo.TypeValue {
var ok bool
rpc.StreamMsg, ok = streamMsgNode.Get().(string)
if !ok {
@@ -541,7 +477,7 @@ func parseServiceRPC(rpcName string, rpcNode *jsongo.Node) (*RPC, error) {
}
// events service (event subscription)
- if eventsNode := rpcNode.At(serviceFieldEvents); eventsNode.GetType() == jsongo.TypeArray {
+ if eventsNode := rpcNode.At(serviceEvents); eventsNode.GetType() == jsongo.TypeArray {
for j := 0; j < eventsNode.Len(); j++ {
event := eventsNode.At(j).Get().(string)
rpc.Events = append(rpc.Events, event)
diff --git a/binapigen/vppapi/util.go b/binapigen/vppapi/util.go
new file mode 100644
index 0000000..87f2e55
--- /dev/null
+++ b/binapigen/vppapi/util.go
@@ -0,0 +1,112 @@
+// 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 vppapi
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ VPPVersionEnvVar = "VPP_VERSION"
+)
+
+// ResolveVPPVersion resolves version of the VPP for target directory.
+//
+// Version resolved here can be overriden by setting VPP_VERSION env var.
+func ResolveVPPVersion(apidir string) string {
+ // check env variable override
+ if ver := os.Getenv(VPPVersionEnvVar); ver != "" {
+ logrus.Debugf("VPP version was manually set to %q via %s env var", ver, VPPVersionEnvVar)
+ return ver
+ }
+
+ // assuming VPP package is installed
+ if path.Clean(apidir) == DefaultDir {
+ version, err := GetVPPVersionInstalled()
+ if err != nil {
+ logrus.Warnf("resolving VPP version from installed package failed: %v", err)
+ } else {
+ logrus.Debugf("resolved VPP version from installed package: %v", version)
+ return version
+ }
+ }
+
+ // check if inside VPP repo
+ repoDir, err := findGitRepoRootDir(apidir)
+ if err != nil {
+ logrus.Warnf("checking VPP git repo failed: %v", err)
+ } else {
+ logrus.Debugf("resolved git repo root directory: %v", repoDir)
+ version, err := GetVPPVersionRepo(repoDir)
+ if err != nil {
+ logrus.Warnf("resolving VPP version from version script failed: %v", err)
+ } else {
+ logrus.Debugf("resolved VPP version from version script: %v", version)
+ return version
+ }
+ }
+
+ // try to read VPP_VERSION file
+ data, err := ioutil.ReadFile(path.Join(repoDir, "VPP_VERSION"))
+ if err == nil {
+ return strings.TrimSpace(string(data))
+ }
+
+ logrus.Warnf("VPP version could not be resolved, you can set it manually using %s env var", VPPVersionEnvVar)
+ return ""
+}
+
+// GetVPPVersionInstalled retrieves VPP version of installed package using dpkg-query.
+func GetVPPVersionInstalled() (string, error) {
+ cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", "vpp")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("dpkg-query command failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+const versionScriptPath = "./src/scripts/version"
+
+// GetVPPVersionRepo retrieves VPP version using script in repo directory.
+func GetVPPVersionRepo(repoDir string) (string, error) {
+ if _, err := os.Stat(versionScriptPath); err != nil {
+ return "", err
+ }
+ cmd := exec.Command(versionScriptPath)
+ cmd.Dir = repoDir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("version script failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+func findGitRepoRootDir(dir string) (string, error) {
+ cmd := exec.Command("git", "rev-parse", "--show-toplevel")
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return "", fmt.Errorf("git command failed: %v\noutput: %s", err, out)
+ }
+ return strings.TrimSpace(string(out)), nil
+}
diff --git a/binapigen/vppapi/parser.go b/binapigen/vppapi/vppapi.go
index 312dd0e..665fa81 100644
--- a/binapigen/vppapi/parser.go
+++ b/binapigen/vppapi/vppapi.go
@@ -19,18 +19,15 @@ import (
"io/ioutil"
"path/filepath"
"strings"
-
- "github.com/sirupsen/logrus"
)
const (
- DefaultAPIDir = "/usr/share/vpp/api"
+ // DefaultDir is default location of API files.
+ DefaultDir = "/usr/share/vpp/api"
)
-const apifileSuffixJson = ".api.json"
-
-// FindFiles returns all input files located in specified directory
-func FindFiles(dir string, deep int) (paths []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 := ioutil.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("reading directory %s failed: %v", dir, err)
@@ -41,43 +38,44 @@ func FindFiles(dir string, deep int) (paths []string, err error) {
if nested, err := FindFiles(nestedDir, deep-1); err != nil {
return nil, err
} else {
- paths = append(paths, nested...)
+ files = append(files, nested...)
}
- } else if strings.HasSuffix(e.Name(), apifileSuffixJson) {
- paths = append(paths, filepath.Join(dir, e.Name()))
+ } else if !e.IsDir() && strings.HasSuffix(e.Name(), ".api.json") {
+ files = append(files, filepath.Join(dir, e.Name()))
}
}
- return paths, nil
+ return files, nil
}
+// Parse parses API files in directory DefaultDir.
func Parse() ([]*File, error) {
- return ParseDir(DefaultAPIDir)
+ 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) {
- files, err := FindFiles(apidir, 1)
+ list, err := FindFiles(apidir, 1)
if err != nil {
return nil, err
}
- logrus.Infof("found %d files in API dir %q", len(files), apidir)
-
- var modules []*File
+ logf("found %d files in API dir %q", len(list), apidir)
- for _, file := range files {
+ var files []*File
+ for _, file := range list {
module, err := ParseFile(file)
if err != nil {
return nil, err
}
- modules = append(modules, module)
+ files = append(files, module)
}
-
- return modules, nil
+ return files, nil
}
-// ParseFile parses API file contents and returns File.
+// ParseFile parses API file and returns File.
func ParseFile(apifile string) (*File, error) {
- if !strings.HasSuffix(apifile, apifileSuffixJson) {
+ if !strings.HasSuffix(apifile, ".api.json") {
return nil, fmt.Errorf("unsupported file format: %q", apifile)
}
@@ -101,7 +99,14 @@ 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) {
+ defer func() {
+ if e := recover(); e != nil {
+ err = fmt.Errorf("panic occurred: %v", e)
+ }
+ }()
+
file, err = parseJSON(data)
if err != nil {
return nil, err
diff --git a/binapigen/vppapi/parser_test.go b/binapigen/vppapi/vppapi_test.go
index 2dc82e4..027cc1f 100644
--- a/binapigen/vppapi/parser_test.go
+++ b/binapigen/vppapi/vppapi_test.go
@@ -46,7 +46,7 @@ func TestReadJson(t *testing.T) {
inputData, err := ioutil.ReadFile("testdata/af_packet.api.json")
Expect(err).ShouldNot(HaveOccurred())
- result, err := parseJSON(inputData)
+ result, err := ParseRaw(inputData)
Expect(err).ShouldNot(HaveOccurred())
Expect(result).ToNot(BeNil())
Expect(result.EnumTypes).To(HaveLen(0))
@@ -60,7 +60,7 @@ func TestReadJsonError(t *testing.T) {
inputData, err := ioutil.ReadFile("testdata/input-read-json-error.json")
Expect(err).ShouldNot(HaveOccurred())
- result, err := parseJSON(inputData)
+ result, err := ParseRaw(inputData)
Expect(err).Should(HaveOccurred())
Expect(result).To(BeNil())
}
@@ -80,17 +80,21 @@ func TestParseFile(t *testing.T) {
if module.Name != "vpe" {
t.Errorf("expected Name=%s, got %v", "vpe", module.Name)
}
+ if module.Path != "testdata/vpe.api.json" {
+ t.Errorf("expected Path=%s, got %v", "testdata/vpe.api.json", module.Path)
+ }
if module.CRC != "0xbd2c94f4" {
t.Errorf("expected CRC=%s, got %v", "0xbd2c94f4", module.CRC)
}
- if module.Version() != "1.6.1" {
- t.Errorf("expected Version=%s, got %v", "1.6.1", module.Version())
+
+ if version := module.Options["version"]; version != "1.6.1" {
+ t.Errorf("expected option[version]=%s, got %v", "1.6.1", version)
}
if len(module.Imports) == 0 {
t.Errorf("expected imports, got none")
}
- if len(module.Options) == 0 {
- t.Errorf("expected options, got none")
+ if len(module.EnumTypes) == 0 {
+ t.Errorf("expected enums, got none")
}
if len(module.AliasTypes) == 0 {
t.Errorf("expected aliases, got none")
@@ -98,12 +102,12 @@ func TestParseFile(t *testing.T) {
if len(module.StructTypes) == 0 {
t.Errorf("expected types, got none")
}
- if len(module.Service.RPCs) == 0 {
- t.Errorf("expected service method, got none")
- }
if len(module.Messages) == 0 {
t.Errorf("expected messages, got none")
}
+ if len(module.Service.RPCs) == 0 {
+ t.Errorf("expected service RPCs, got none")
+ }
}
func TestParseFileUnsupported(t *testing.T) {