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))
76 for _, aliasType := range apifile.AliasTypes {
77 file.Aliases = append(file.Aliases, newAlias(gen, file, aliasType))
79 for _, structType := range apifile.StructTypes {
80 file.Structs = append(file.Structs, newStruct(gen, file, structType))
82 for _, unionType := range apifile.UnionTypes {
83 file.Unions = append(file.Unions, newUnion(gen, file, unionType))
86 for _, msg := range apifile.Messages {
87 file.Messages = append(file.Messages, newMessage(gen, file, msg))
89 if apifile.Service != nil {
90 file.Service = newService(gen, file, *apifile.Service)
93 for _, t := range file.Aliases {
94 if err := t.resolveDependencies(gen); err != nil {
98 for _, t := range file.Structs {
99 if err := t.resolveDependencies(gen); err != nil {
103 for _, t := range file.Unions {
104 if err := t.resolveDependencies(gen); err != nil {
108 for _, m := range file.Messages {
109 if err := m.resolveDependencies(gen); err != nil {
113 if file.Service != nil {
114 for _, rpc := range file.Service.RPCs {
115 if err := rpc.resolveMessages(gen); err != nil {
124 func (file *File) isTypesFile() bool {
125 return strings.HasSuffix(file.Desc.Name, "_types")
128 func (file *File) hasService() bool {
129 return file.Service != nil && len(file.Service.RPCs) > 0
132 func (file *File) importedFiles(gen *Generator) []*File {
134 for _, imp := range file.Imports {
135 impFile, ok := gen.FilesByName[imp]
137 logf("file %s import %s not found API files", file.Desc.Name, imp)
140 files = append(files, impFile)
145 func (file *File) dependsOnFile(gen *Generator, dep string) bool {
146 for _, imp := range file.Imports {
150 impFile, ok := gen.FilesByName[imp]
151 if ok && impFile.dependsOnFile(gen, dep) {
159 enumFlagSuffix = "_flags"
162 func isEnumFlag(enum *Enum) bool {
163 return strings.HasSuffix(enum.Name, enumFlagSuffix)
172 func newEnum(gen *Generator, file *File, apitype vppapi.EnumType) *Enum {
176 GoName: camelCaseName(apitype.Name),
177 GoImportPath: file.GoImportPath,
180 gen.enumsByName[typ.Name] = typ
194 func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias {
198 GoName: camelCaseName(apitype.Name),
199 GoImportPath: file.GoImportPath,
202 gen.aliasesByName[typ.Name] = typ
206 func (a *Alias) resolveDependencies(gen *Generator) error {
207 if err := a.resolveType(gen); err != nil {
208 return fmt.Errorf("unable to resolve field: %w", err)
213 func (a *Alias) resolveType(gen *Generator) error {
214 if _, ok := BaseTypesGo[a.Type]; ok {
217 typ := fromApiType(a.Type)
218 if t, ok := gen.structsByName[typ]; ok {
222 if t, ok := gen.unionsByName[typ]; ok {
226 return fmt.Errorf("unknown type: %q", a.Type)
237 func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct {
241 GoName: camelCaseName(apitype.Name),
242 GoImportPath: file.GoImportPath,
245 gen.structsByName[typ.Name] = typ
246 for i, fieldType := range apitype.Fields {
247 field := newField(gen, file, typ, fieldType, i)
248 typ.Fields = append(typ.Fields, field)
253 func (m *Struct) resolveDependencies(gen *Generator) (err error) {
254 for _, field := range m.Fields {
255 if err := field.resolveDependencies(gen); err != nil {
256 return fmt.Errorf("unable to resolve for struct %s: %w", m.Name, err)
270 func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union {
274 GoName: withSuffix(camelCaseName(apitype.Name), "Union"),
275 GoImportPath: file.GoImportPath,
278 gen.unionsByName[typ.Name] = typ
279 for i, fieldType := range apitype.Fields {
280 field := newField(gen, file, typ, fieldType, i)
281 typ.Fields = append(typ.Fields, field)
286 func (m *Union) resolveDependencies(gen *Generator) (err error) {
287 for _, field := range m.Fields {
288 if err := field.resolveDependencies(gen); err != nil {
295 // msgType determines message header fields
299 msgTypeBase msgType = iota // msg_id
300 msgTypeRequest // msg_id, client_index, context
301 msgTypeReply // msg_id, context
302 msgTypeEvent // msg_id, client_index
305 // common message fields
307 fieldMsgID = "_vl_msg_id"
308 fieldClientIndex = "client_index"
309 fieldContext = "context"
310 fieldRetval = "retval"
315 optFieldDefault = "default"
318 type Message struct {
330 func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
333 CRC: strings.TrimPrefix(apitype.CRC, "0x"),
334 GoIdent: newGoIdent(file, apitype.Name),
336 gen.messagesByName[apitype.Name] = msg
338 for _, fieldType := range apitype.Fields {
340 // skip header fields
341 switch strings.ToLower(fieldType.Name) {
342 case fieldMsgID, fieldClientIndex, fieldContext:
347 field := newField(gen, file, msg, fieldType, n)
348 msg.Fields = append(msg.Fields, field)
353 func (m *Message) resolveDependencies(gen *Generator) (err error) {
354 if m.msgType, err = getMsgType(m.Message); err != nil {
357 for _, field := range m.Fields {
358 if err := field.resolveDependencies(gen); err != nil {
365 func getMsgType(m vppapi.Message) (msgType, error) {
366 if len(m.Fields) == 0 {
367 return msgType(-1), fmt.Errorf("message %s has no fields", m.Name)
370 var wasClientIndex bool
371 for i, field := range m.Fields {
374 if field.Name != fieldMsgID {
375 return msgType(-1), fmt.Errorf("message %s is missing ID field", m.Name)
378 if field.Name == fieldClientIndex {
379 // "client_index" as the second member,
380 // this might be an event message or a request
382 wasClientIndex = true
383 } else if field.Name == fieldContext {
384 // reply needs "context" as the second member
388 if field.Name == fieldContext && wasClientIndex {
389 // request needs "client_index" as the second member
390 // and "context" as the third member
398 func getRetvalField(m *Message) *Field {
399 for _, field := range m.Fields {
400 if field.Name == fieldRetval {
407 // Field represents a field for message or struct/union types.
413 // Index defines field index in parent.
416 // DefaultValue is a default value of field or
417 // nil if default value is not defined for field.
418 DefaultValue interface{}
420 // Reference to actual type of this field.
422 // For fields with built-in types all of these are nil,
423 // otherwise only one is set to non-nil value.
429 // Parent in which this field is declared.
430 ParentMessage *Message
434 // Field reference for fields with variable size.
439 func newField(gen *Generator, file *File, parent interface{}, apitype vppapi.Field, index int) *Field {
442 GoName: camelCaseName(apitype.Name),
445 switch p := parent.(type) {
447 typ.ParentMessage = p
453 panic(fmt.Sprintf("invalid field parent: %T", parent))
455 if apitype.Meta != nil {
456 if val, ok := apitype.Meta[optFieldDefault]; ok {
457 typ.DefaultValue = val
463 func (f *Field) resolveDependencies(gen *Generator) error {
464 if err := f.resolveType(gen); err != nil {
465 return fmt.Errorf("unable to resolve field type: %w", err)
467 if err := f.resolveFields(gen); err != nil {
468 return fmt.Errorf("unable to resolve fields: %w", err)
473 func (f *Field) resolveType(gen *Generator) error {
474 if _, ok := BaseTypesGo[f.Type]; ok {
477 typ := fromApiType(f.Type)
478 if t, ok := gen.structsByName[typ]; ok {
482 if t, ok := gen.enumsByName[typ]; ok {
486 if t, ok := gen.aliasesByName[typ]; ok {
490 if t, ok := gen.unionsByName[typ]; ok {
494 return fmt.Errorf("unknown type: %q", f.Type)
497 func (f *Field) resolveFields(gen *Generator) error {
499 if f.ParentMessage != nil {
500 fields = f.ParentMessage.Fields
501 } else if f.ParentStruct != nil {
502 fields = f.ParentStruct.Fields
504 if f.SizeFrom != "" {
505 for _, field := range fields {
506 if field.Name == f.SizeFrom {
507 f.FieldSizeFrom = field
512 for _, field := range fields {
513 if field.SizeFrom == f.Name {
514 f.FieldSizeOf = field
522 type Service struct {
528 func newService(gen *Generator, file *File, apitype vppapi.Service) *Service {
532 for _, rpc := range apitype.RPCs {
533 svc.RPCs = append(svc.RPCs, newRpc(file, svc, rpc))
539 serviceNoReply = "null"
554 func newRpc(file *File, service *Service, apitype vppapi.RPC) *RPC {
557 GoName: camelCaseName(apitype.Request),
563 func (rpc *RPC) resolveMessages(gen *Generator) error {
564 msg, ok := gen.messagesByName[rpc.VPP.Request]
566 return fmt.Errorf("rpc %v: no message for request type %v", rpc.GoName, rpc.VPP.Request)
570 if rpc.VPP.Reply != "" && rpc.VPP.Reply != serviceNoReply {
571 msg, ok := gen.messagesByName[rpc.VPP.Reply]
573 return fmt.Errorf("rpc %v: no message for reply type %v", rpc.GoName, rpc.VPP.Reply)
577 if rpc.VPP.StreamMsg != "" {
578 msg, ok := gen.messagesByName[rpc.VPP.StreamMsg]
580 return fmt.Errorf("rpc %v: no message for stream type %v", rpc.GoName, rpc.VPP.StreamMsg)
587 // GoIdent is a Go identifier, consisting of a name and import path.
588 // The name is a single identifier and may not be a dot-qualified selector.
589 type GoIdent struct {
591 GoImportPath GoImportPath
594 func (id GoIdent) String() string {
595 return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName)
598 func newGoIdent(f *File, fullName string) GoIdent {
599 name := strings.TrimPrefix(fullName, string(f.PackageName)+".")
601 GoName: camelCaseName(name),
602 GoImportPath: f.GoImportPath,
606 // GoImportPath is a Go import path for a package.
607 type GoImportPath string
609 func (p GoImportPath) String() string {
610 return strconv.Quote(string(p))
613 func (p GoImportPath) Ident(s string) GoIdent {
614 return GoIdent{GoName: s, GoImportPath: p}
617 type GoPackageName string
619 func cleanPackageName(name string) GoPackageName {
620 return GoPackageName(sanitizedName(name))
623 // baseName returns the last path element of the name, with the last dotted suffix removed.
624 func baseName(name string) string {
625 // First, find the last element
626 if i := strings.LastIndex(name, "/"); i >= 0 {
629 // Now drop the suffix
630 if i := strings.LastIndex(name, "."); i >= 0 {
636 // normalizeImport returns the last path element of the import, with all dotted suffixes removed.
637 func normalizeImport(imp string) string {
639 if idx := strings.Index(imp, "."); idx >= 0 {