1 // Copyright (c) 2020 Cisco and/or its affiliates.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
23 "git.fd.io/govpp.git/binapigen/vppapi"
26 // generatedCodeVersion indicates a version of the generated code.
27 // It is incremented whenever an incompatibility between the generated code and
28 // GoVPP api package is introduced; the generated code references
29 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
30 const generatedCodeVersion = 2
34 optFileVersion = "version"
42 PackageName GoPackageName
43 GoImportPath GoImportPath
57 func newFile(gen *Generator, apifile *vppapi.File, packageName GoPackageName, importPath GoImportPath) (*File, error) {
60 PackageName: packageName,
61 GoImportPath: importPath,
63 if apifile.Options != nil {
64 file.Version = apifile.Options[optFileVersion]
67 file.FilenamePrefix = path.Join(gen.opts.OutputDir, file.Desc.Name)
69 for _, imp := range apifile.Imports {
70 file.Imports = append(file.Imports, normalizeImport(imp))
73 for _, enumType := range apifile.EnumTypes {
74 file.Enums = append(file.Enums, newEnum(gen, file, enumType, false))
76 for _, enumflagType := range apifile.EnumflagTypes {
77 file.Enums = append(file.Enums, newEnum(gen, file, enumflagType, true))
79 for _, aliasType := range apifile.AliasTypes {
80 file.Aliases = append(file.Aliases, newAlias(gen, file, aliasType))
82 for _, structType := range apifile.StructTypes {
83 file.Structs = append(file.Structs, newStruct(gen, file, structType))
85 for _, unionType := range apifile.UnionTypes {
86 file.Unions = append(file.Unions, newUnion(gen, file, unionType))
89 for _, msg := range apifile.Messages {
90 file.Messages = append(file.Messages, newMessage(gen, file, msg))
92 if apifile.Service != nil {
93 file.Service = newService(gen, file, *apifile.Service)
96 for _, t := range file.Aliases {
97 if err := t.resolveDependencies(gen); err != nil {
101 for _, t := range file.Structs {
102 if err := t.resolveDependencies(gen); err != nil {
106 for _, t := range file.Unions {
107 if err := t.resolveDependencies(gen); err != nil {
111 for _, m := range file.Messages {
112 if err := m.resolveDependencies(gen); err != nil {
116 if file.Service != nil {
117 for _, rpc := range file.Service.RPCs {
118 if err := rpc.resolveMessages(gen); err != nil {
127 func (file *File) isTypesFile() bool {
128 return strings.HasSuffix(file.Desc.Name, "_types")
131 func (file *File) hasService() bool {
132 return file.Service != nil && len(file.Service.RPCs) > 0
135 func (file *File) importedFiles(gen *Generator) []*File {
137 for _, imp := range file.Imports {
138 impFile, ok := gen.FilesByName[imp]
140 logf("file %s import %s not found API files", file.Desc.Name, imp)
143 files = append(files, impFile)
148 func (file *File) dependsOnFile(gen *Generator, dep string) bool {
149 for _, imp := range file.Imports {
153 impFile, ok := gen.FilesByName[imp]
154 if ok && impFile.dependsOnFile(gen, dep) {
162 enumFlagSuffix = "_flags"
165 func isEnumFlag(enum *Enum) bool {
166 return strings.HasSuffix(enum.Name, enumFlagSuffix)
177 func newEnum(gen *Generator, file *File, apitype vppapi.EnumType, isFlag bool) *Enum {
181 GoName: camelCaseName(apitype.Name),
182 GoImportPath: file.GoImportPath,
186 gen.enumsByName[typ.Name] = typ
200 func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias {
204 GoName: camelCaseName(apitype.Name),
205 GoImportPath: file.GoImportPath,
208 gen.aliasesByName[typ.Name] = typ
212 func (a *Alias) resolveDependencies(gen *Generator) error {
213 if err := a.resolveType(gen); err != nil {
214 return fmt.Errorf("unable to resolve field: %w", err)
219 func (a *Alias) resolveType(gen *Generator) error {
220 if _, ok := BaseTypesGo[a.Type]; ok {
223 typ := fromApiType(a.Type)
224 if t, ok := gen.structsByName[typ]; ok {
228 if t, ok := gen.unionsByName[typ]; ok {
232 return fmt.Errorf("unknown type: %q", a.Type)
243 func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct {
247 GoName: camelCaseName(apitype.Name),
248 GoImportPath: file.GoImportPath,
251 gen.structsByName[typ.Name] = typ
252 for i, fieldType := range apitype.Fields {
253 field := newField(gen, file, typ, fieldType, i)
254 typ.Fields = append(typ.Fields, field)
259 func (m *Struct) resolveDependencies(gen *Generator) (err error) {
260 for _, field := range m.Fields {
261 if err := field.resolveDependencies(gen); err != nil {
262 return fmt.Errorf("unable to resolve for struct %s: %w", m.Name, err)
276 func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union {
280 GoName: withSuffix(camelCaseName(apitype.Name), "Union"),
281 GoImportPath: file.GoImportPath,
284 gen.unionsByName[typ.Name] = typ
285 for i, fieldType := range apitype.Fields {
286 field := newField(gen, file, typ, fieldType, i)
287 typ.Fields = append(typ.Fields, field)
292 func (m *Union) resolveDependencies(gen *Generator) (err error) {
293 for _, field := range m.Fields {
294 if err := field.resolveDependencies(gen); err != nil {
301 // msgType determines message header fields
305 msgTypeBase msgType = iota // msg_id
306 msgTypeRequest // msg_id, client_index, context
307 msgTypeReply // msg_id, context
308 msgTypeEvent // msg_id, client_index
311 // common message fields
313 fieldMsgID = "_vl_msg_id"
314 fieldClientIndex = "client_index"
315 fieldContext = "context"
316 fieldRetval = "retval"
321 optFieldDefault = "default"
324 type Message struct {
336 func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
339 CRC: strings.TrimPrefix(apitype.CRC, "0x"),
340 GoIdent: newGoIdent(file, apitype.Name),
342 gen.messagesByName[apitype.Name] = msg
344 for _, fieldType := range apitype.Fields {
346 // skip header fields
347 switch strings.ToLower(fieldType.Name) {
348 case fieldMsgID, fieldClientIndex, fieldContext:
353 field := newField(gen, file, msg, fieldType, n)
354 msg.Fields = append(msg.Fields, field)
359 func (m *Message) resolveDependencies(gen *Generator) (err error) {
360 if m.msgType, err = getMsgType(m.Message); err != nil {
363 for _, field := range m.Fields {
364 if err := field.resolveDependencies(gen); err != nil {
371 func getMsgType(m vppapi.Message) (msgType, error) {
372 if len(m.Fields) == 0 {
373 return msgType(-1), fmt.Errorf("message %s has no fields", m.Name)
376 var wasClientIndex bool
377 for i, field := range m.Fields {
380 if field.Name != fieldMsgID {
381 return msgType(-1), fmt.Errorf("message %s is missing ID field", m.Name)
384 if field.Name == fieldClientIndex {
385 // "client_index" as the second member,
386 // this might be an event message or a request
388 wasClientIndex = true
389 } else if field.Name == fieldContext {
390 // reply needs "context" as the second member
394 if field.Name == fieldContext && wasClientIndex {
395 // request needs "client_index" as the second member
396 // and "context" as the third member
404 func getRetvalField(m *Message) *Field {
405 for _, field := range m.Fields {
406 if field.Name == fieldRetval {
413 // Field represents a field for message or struct/union types.
419 // Index defines field index in parent.
422 // DefaultValue is a default value of field or
423 // nil if default value is not defined for field.
424 DefaultValue interface{}
426 // Reference to actual type of this field.
428 // For fields with built-in types all of these are nil,
429 // otherwise only one is set to non-nil value.
435 // Parent in which this field is declared.
436 ParentMessage *Message
440 // Field reference for fields with variable size.
445 func newField(gen *Generator, file *File, parent interface{}, apitype vppapi.Field, index int) *Field {
448 GoName: camelCaseName(apitype.Name),
451 switch p := parent.(type) {
453 typ.ParentMessage = p
459 panic(fmt.Sprintf("invalid field parent: %T", parent))
461 if apitype.Meta != nil {
462 if val, ok := apitype.Meta[optFieldDefault]; ok {
463 typ.DefaultValue = val
469 func (f *Field) resolveDependencies(gen *Generator) error {
470 if err := f.resolveType(gen); err != nil {
471 return fmt.Errorf("unable to resolve field type: %w", err)
473 if err := f.resolveFields(gen); err != nil {
474 return fmt.Errorf("unable to resolve fields: %w", err)
479 func (f *Field) resolveType(gen *Generator) error {
480 if _, ok := BaseTypesGo[f.Type]; ok {
483 typ := fromApiType(f.Type)
484 if t, ok := gen.structsByName[typ]; ok {
488 if t, ok := gen.enumsByName[typ]; ok {
492 if t, ok := gen.aliasesByName[typ]; ok {
496 if t, ok := gen.unionsByName[typ]; ok {
500 return fmt.Errorf("unknown type: %q", f.Type)
503 func (f *Field) resolveFields(gen *Generator) error {
505 if f.ParentMessage != nil {
506 fields = f.ParentMessage.Fields
507 } else if f.ParentStruct != nil {
508 fields = f.ParentStruct.Fields
510 if f.SizeFrom != "" {
511 for _, field := range fields {
512 if field.Name == f.SizeFrom {
513 f.FieldSizeFrom = field
518 for _, field := range fields {
519 if field.SizeFrom == f.Name {
520 f.FieldSizeOf = field
528 type Service struct {
534 func newService(gen *Generator, file *File, apitype vppapi.Service) *Service {
538 for _, rpc := range apitype.RPCs {
539 svc.RPCs = append(svc.RPCs, newRpc(file, svc, rpc))
545 serviceNoReply = "null"
560 func newRpc(file *File, service *Service, apitype vppapi.RPC) *RPC {
563 GoName: camelCaseName(apitype.Request),
569 func (rpc *RPC) resolveMessages(gen *Generator) error {
570 msg, ok := gen.messagesByName[rpc.VPP.Request]
572 return fmt.Errorf("rpc %v: no message for request type %v", rpc.GoName, rpc.VPP.Request)
576 if rpc.VPP.Reply != "" && rpc.VPP.Reply != serviceNoReply {
577 msg, ok := gen.messagesByName[rpc.VPP.Reply]
579 return fmt.Errorf("rpc %v: no message for reply type %v", rpc.GoName, rpc.VPP.Reply)
583 if rpc.VPP.StreamMsg != "" {
584 msg, ok := gen.messagesByName[rpc.VPP.StreamMsg]
586 return fmt.Errorf("rpc %v: no message for stream type %v", rpc.GoName, rpc.VPP.StreamMsg)
593 // GoIdent is a Go identifier, consisting of a name and import path.
594 // The name is a single identifier and may not be a dot-qualified selector.
595 type GoIdent struct {
597 GoImportPath GoImportPath
600 func (id GoIdent) String() string {
601 return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName)
604 func newGoIdent(f *File, fullName string) GoIdent {
605 name := strings.TrimPrefix(fullName, string(f.PackageName)+".")
607 GoName: camelCaseName(name),
608 GoImportPath: f.GoImportPath,
612 // GoImportPath is a Go import path for a package.
613 type GoImportPath string
615 func (p GoImportPath) String() string {
616 return strconv.Quote(string(p))
619 func (p GoImportPath) Ident(s string) GoIdent {
620 return GoIdent{GoName: s, GoImportPath: p}
623 type GoPackageName string
625 func cleanPackageName(name string) GoPackageName {
626 return GoPackageName(sanitizedName(name))
629 // baseName returns the last path element of the name, with the last dotted suffix removed.
630 func baseName(name string) string {
631 // First, find the last element
632 if i := strings.LastIndex(name, "/"); i >= 0 {
635 // Now drop the suffix
636 if i := strings.LastIndex(name, "."); i >= 0 {
642 // normalizeImport returns the last path element of the import, with all dotted suffixes removed.
643 func normalizeImport(imp string) string {
645 if idx := strings.Index(imp, "."); idx >= 0 {