1package builder 2 3import ( 4 "fmt" 5 "sort" 6 "strings" 7 "sync/atomic" 8 9 "github.com/golang/protobuf/proto" 10 dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" 11 12 "github.com/jhump/protoreflect/desc" 13 "github.com/jhump/protoreflect/desc/internal" 14) 15 16var uniqueFileCounter uint64 17 18func uniqueFileName() string { 19 i := atomic.AddUint64(&uniqueFileCounter, 1) 20 return fmt.Sprintf("{generated-file-%04x}.proto", i) 21} 22 23func makeUnique(name string, existingNames map[string]struct{}) string { 24 i := 1 25 n := name 26 for { 27 if _, ok := existingNames[n]; !ok { 28 return n 29 } 30 n = fmt.Sprintf("%s(%d)", name, i) 31 i++ 32 } 33} 34 35// FileBuilder is a builder used to construct a desc.FileDescriptor. This is the 36// root of the hierarchy. All other descriptors belong to a file, and thus all 37// other builders also belong to a file. 38// 39// If a builder is *not* associated with a file, the resulting descriptor will 40// be associated with a synthesized file that contains only the built descriptor 41// and its ancestors. This means that such descriptors will have no associated 42// package name. 43// 44// To create a new FileBuilder, use NewFile. 45type FileBuilder struct { 46 name string 47 48 IsProto3 bool 49 Package string 50 Options *dpb.FileOptions 51 52 comments Comments 53 SyntaxComments Comments 54 PackageComments Comments 55 56 messages []*MessageBuilder 57 extensions []*FieldBuilder 58 enums []*EnumBuilder 59 services []*ServiceBuilder 60 symbols map[string]Builder 61 62 explicitDeps map[*FileBuilder]struct{} 63 explicitImports map[*desc.FileDescriptor]struct{} 64} 65 66// NewFile creates a new FileBuilder for a file with the given name. The 67// name can be blank, which indicates a unique name should be generated for it. 68func NewFile(name string) *FileBuilder { 69 return &FileBuilder{ 70 name: name, 71 symbols: map[string]Builder{}, 72 } 73} 74 75// FromFile returns a FileBuilder that is effectively a copy of the given 76// descriptor. Note that builders do not retain full source code info, even if 77// the given descriptor included it. Instead, comments are extracted from the 78// given descriptor's source info (if present) and, when built, the resulting 79// descriptor will have just the comment info (no location information). 80func FromFile(fd *desc.FileDescriptor) (*FileBuilder, error) { 81 fb := NewFile(fd.GetName()) 82 fb.IsProto3 = fd.IsProto3() 83 fb.Package = fd.GetPackage() 84 fb.Options = fd.GetFileOptions() 85 setComments(&fb.comments, fd.GetSourceInfo()) 86 87 // find syntax and package comments, too 88 for _, loc := range fd.AsFileDescriptorProto().GetSourceCodeInfo().GetLocation() { 89 if len(loc.Path) == 1 { 90 if loc.Path[0] == internal.File_syntaxTag { 91 setComments(&fb.SyntaxComments, loc) 92 } else if loc.Path[0] == internal.File_packageTag { 93 setComments(&fb.PackageComments, loc) 94 } 95 } 96 } 97 98 // add imports explicitly 99 for _, dep := range fd.GetDependencies() { 100 fb.AddImportedDependency(dep) 101 } 102 103 localMessages := map[*desc.MessageDescriptor]*MessageBuilder{} 104 localEnums := map[*desc.EnumDescriptor]*EnumBuilder{} 105 106 for _, md := range fd.GetMessageTypes() { 107 if mb, err := fromMessage(md, localMessages, localEnums); err != nil { 108 return nil, err 109 } else if err := fb.TryAddMessage(mb); err != nil { 110 return nil, err 111 } 112 } 113 for _, ed := range fd.GetEnumTypes() { 114 if eb, err := fromEnum(ed, localEnums); err != nil { 115 return nil, err 116 } else if err := fb.TryAddEnum(eb); err != nil { 117 return nil, err 118 } 119 } 120 for _, exd := range fd.GetExtensions() { 121 if exb, err := fromField(exd); err != nil { 122 return nil, err 123 } else if err := fb.TryAddExtension(exb); err != nil { 124 return nil, err 125 } 126 } 127 for _, sd := range fd.GetServices() { 128 if sb, err := fromService(sd); err != nil { 129 return nil, err 130 } else if err := fb.TryAddService(sb); err != nil { 131 return nil, err 132 } 133 } 134 135 // we've converted everything, so now we update all foreign type references 136 // to be local type references if possible 137 for _, mb := range fb.messages { 138 updateLocalRefsInMessage(mb, localMessages, localEnums) 139 } 140 for _, exb := range fb.extensions { 141 updateLocalRefsInField(exb, localMessages, localEnums) 142 } 143 for _, sb := range fb.services { 144 for _, mtb := range sb.methods { 145 updateLocalRefsInRpcType(mtb.ReqType, localMessages) 146 updateLocalRefsInRpcType(mtb.RespType, localMessages) 147 } 148 } 149 150 return fb, nil 151} 152 153func updateLocalRefsInMessage(mb *MessageBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) { 154 for _, b := range mb.fieldsAndOneOfs { 155 if flb, ok := b.(*FieldBuilder); ok { 156 updateLocalRefsInField(flb, localMessages, localEnums) 157 } else { 158 oob := b.(*OneOfBuilder) 159 for _, flb := range oob.choices { 160 updateLocalRefsInField(flb, localMessages, localEnums) 161 } 162 } 163 } 164 for _, nmb := range mb.nestedMessages { 165 updateLocalRefsInMessage(nmb, localMessages, localEnums) 166 } 167 for _, exb := range mb.nestedExtensions { 168 updateLocalRefsInField(exb, localMessages, localEnums) 169 } 170} 171 172func updateLocalRefsInField(flb *FieldBuilder, localMessages map[*desc.MessageDescriptor]*MessageBuilder, localEnums map[*desc.EnumDescriptor]*EnumBuilder) { 173 if flb.fieldType.foreignMsgType != nil { 174 if mb, ok := localMessages[flb.fieldType.foreignMsgType]; ok { 175 flb.fieldType.foreignMsgType = nil 176 flb.fieldType.localMsgType = mb 177 } 178 } 179 if flb.fieldType.foreignEnumType != nil { 180 if eb, ok := localEnums[flb.fieldType.foreignEnumType]; ok { 181 flb.fieldType.foreignEnumType = nil 182 flb.fieldType.localEnumType = eb 183 } 184 } 185 if flb.foreignExtendee != nil { 186 if mb, ok := localMessages[flb.foreignExtendee]; ok { 187 flb.foreignExtendee = nil 188 flb.localExtendee = mb 189 } 190 } 191 if flb.msgType != nil { 192 updateLocalRefsInMessage(flb.msgType, localMessages, localEnums) 193 } 194} 195 196func updateLocalRefsInRpcType(rpcType *RpcType, localMessages map[*desc.MessageDescriptor]*MessageBuilder) { 197 if rpcType.foreignType != nil { 198 if mb, ok := localMessages[rpcType.foreignType]; ok { 199 rpcType.foreignType = nil 200 rpcType.localType = mb 201 } 202 } 203} 204 205// GetName returns the name of the file. It may include relative path 206// information, too. 207func (fb *FileBuilder) GetName() string { 208 return fb.name 209} 210 211// SetName changes this file's name, returning the file builder for method 212// chaining. 213func (fb *FileBuilder) SetName(newName string) *FileBuilder { 214 fb.name = newName 215 return fb 216} 217 218// TrySetName changes this file's name. It always returns nil since renaming 219// a file cannot fail. (It is specified to return error to satisfy the Builder 220// interface.) 221func (fb *FileBuilder) TrySetName(newName string) error { 222 fb.name = newName 223 return nil 224} 225 226// GetParent always returns nil since files are the roots of builder 227// hierarchies. 228func (fb *FileBuilder) GetParent() Builder { 229 return nil 230} 231 232func (fb *FileBuilder) setParent(parent Builder) { 233 if parent != nil { 234 panic("files cannot have parent elements") 235 } 236} 237 238// GetComments returns comments associated with the file itself and not any 239// particular element therein. (Note that such a comment will not be rendered by 240// the protoprint package.) 241func (fb *FileBuilder) GetComments() *Comments { 242 return &fb.comments 243} 244 245// SetComments sets the comments associated with the file itself, not any 246// particular element therein. (Note that such a comment will not be rendered by 247// the protoprint package.) This method returns the file, for method chaining. 248func (fb *FileBuilder) SetComments(c Comments) *FileBuilder { 249 fb.comments = c 250 return fb 251} 252 253// SetSyntaxComments sets the comments associated with the syntax declaration 254// element (which, if present, is required to be the first element in a proto 255// file). This method returns the file, for method chaining. 256func (fb *FileBuilder) SetSyntaxComments(c Comments) *FileBuilder { 257 fb.SyntaxComments = c 258 return fb 259} 260 261// SetPackageComments sets the comments associated with the package declaration 262// element. (This comment will not be rendered if the file's declared package is 263// empty.) This method returns the file, for method chaining. 264func (fb *FileBuilder) SetPackageComments(c Comments) *FileBuilder { 265 fb.PackageComments = c 266 return fb 267} 268 269// GetFile implements the Builder interface and always returns this file. 270func (fb *FileBuilder) GetFile() *FileBuilder { 271 return fb 272} 273 274// GetChildren returns builders for all nested elements, including all top-level 275// messages, enums, extensions, and services. 276func (fb *FileBuilder) GetChildren() []Builder { 277 var ch []Builder 278 for _, mb := range fb.messages { 279 ch = append(ch, mb) 280 } 281 for _, exb := range fb.extensions { 282 ch = append(ch, exb) 283 } 284 for _, eb := range fb.enums { 285 ch = append(ch, eb) 286 } 287 for _, sb := range fb.services { 288 ch = append(ch, sb) 289 } 290 return ch 291} 292 293func (fb *FileBuilder) findChild(name string) Builder { 294 return fb.symbols[name] 295} 296 297func (fb *FileBuilder) removeChild(b Builder) { 298 if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb { 299 return 300 } 301 302 switch b.(type) { 303 case *MessageBuilder: 304 fb.messages = deleteBuilder(b.GetName(), fb.messages).([]*MessageBuilder) 305 case *FieldBuilder: 306 fb.extensions = deleteBuilder(b.GetName(), fb.extensions).([]*FieldBuilder) 307 case *EnumBuilder: 308 fb.enums = deleteBuilder(b.GetName(), fb.enums).([]*EnumBuilder) 309 case *ServiceBuilder: 310 fb.services = deleteBuilder(b.GetName(), fb.services).([]*ServiceBuilder) 311 } 312 delete(fb.symbols, b.GetName()) 313 b.setParent(nil) 314} 315 316func (fb *FileBuilder) renamedChild(b Builder, oldName string) error { 317 if p, ok := b.GetParent().(*FileBuilder); !ok || p != fb { 318 return nil 319 } 320 321 if err := fb.addSymbol(b); err != nil { 322 return err 323 } 324 delete(fb.symbols, oldName) 325 return nil 326} 327 328func (fb *FileBuilder) addSymbol(b Builder) error { 329 if ex, ok := fb.symbols[b.GetName()]; ok { 330 return fmt.Errorf("file %q already contains element (%T) named %q", fb.GetName(), ex, b.GetName()) 331 } 332 fb.symbols[b.GetName()] = b 333 return nil 334} 335 336func (fb *FileBuilder) findFullyQualifiedElement(fqn string) Builder { 337 if fb.Package != "" { 338 if !strings.HasPrefix(fqn, fb.Package+".") { 339 return nil 340 } 341 fqn = fqn[len(fb.Package)+1:] 342 } 343 names := strings.Split(fqn, ".") 344 var b Builder = fb 345 for b != nil && len(names) > 0 { 346 b = b.findChild(names[0]) 347 names = names[1:] 348 } 349 return b 350} 351 352// GetMessage returns the top-level message with the given name. If no such 353// message exists in the file, nil is returned. 354func (fb *FileBuilder) GetMessage(name string) *MessageBuilder { 355 b := fb.symbols[name] 356 if mb, ok := b.(*MessageBuilder); ok { 357 return mb 358 } else { 359 return nil 360 } 361} 362 363// RemoveMessage removes the top-level message with the given name. If no such 364// message exists in the file, this is a no-op. This returns the file builder, 365// for method chaining. 366func (fb *FileBuilder) RemoveMessage(name string) *FileBuilder { 367 fb.TryRemoveMessage(name) 368 return fb 369} 370 371// TryRemoveMessage removes the top-level message with the given name and 372// returns false if the file has no such message. 373func (fb *FileBuilder) TryRemoveMessage(name string) bool { 374 b := fb.symbols[name] 375 if mb, ok := b.(*MessageBuilder); ok { 376 fb.removeChild(mb) 377 return true 378 } 379 return false 380} 381 382// AddMessage adds the given message to this file. If an error prevents the 383// message from being added, this method panics. This returns the file builder, 384// for method chaining. 385func (fb *FileBuilder) AddMessage(mb *MessageBuilder) *FileBuilder { 386 if err := fb.TryAddMessage(mb); err != nil { 387 panic(err) 388 } 389 return fb 390} 391 392// TryAddMessage adds the given message to this file, returning any error that 393// prevents the message from being added (such as a name collision with another 394// element already added to the file). 395func (fb *FileBuilder) TryAddMessage(mb *MessageBuilder) error { 396 if err := fb.addSymbol(mb); err != nil { 397 return err 398 } 399 Unlink(mb) 400 mb.setParent(fb) 401 fb.messages = append(fb.messages, mb) 402 return nil 403} 404 405// GetExtension returns the top-level extension with the given name. If no such 406// extension exists in the file, nil is returned. 407func (fb *FileBuilder) GetExtension(name string) *FieldBuilder { 408 b := fb.symbols[name] 409 if exb, ok := b.(*FieldBuilder); ok { 410 return exb 411 } else { 412 return nil 413 } 414} 415 416// RemoveExtension removes the top-level extension with the given name. If no 417// such extension exists in the file, this is a no-op. This returns the file 418// builder, for method chaining. 419func (fb *FileBuilder) RemoveExtension(name string) *FileBuilder { 420 fb.TryRemoveExtension(name) 421 return fb 422} 423 424// TryRemoveExtension removes the top-level extension with the given name and 425// returns false if the file has no such extension. 426func (fb *FileBuilder) TryRemoveExtension(name string) bool { 427 b := fb.symbols[name] 428 if exb, ok := b.(*FieldBuilder); ok { 429 fb.removeChild(exb) 430 return true 431 } 432 return false 433} 434 435// AddExtension adds the given extension to this file. If an error prevents the 436// extension from being added, this method panics. This returns the file 437// builder, for method chaining. 438func (fb *FileBuilder) AddExtension(exb *FieldBuilder) *FileBuilder { 439 if err := fb.TryAddExtension(exb); err != nil { 440 panic(err) 441 } 442 return fb 443} 444 445// TryAddExtension adds the given extension to this file, returning any error 446// that prevents the extension from being added (such as a name collision with 447// another element already added to the file). 448func (fb *FileBuilder) TryAddExtension(exb *FieldBuilder) error { 449 if !exb.IsExtension() { 450 return fmt.Errorf("field %s is not an extension", exb.GetName()) 451 } 452 if err := fb.addSymbol(exb); err != nil { 453 return err 454 } 455 Unlink(exb) 456 exb.setParent(fb) 457 fb.extensions = append(fb.extensions, exb) 458 return nil 459} 460 461// GetEnum returns the top-level enum with the given name. If no such enum 462// exists in the file, nil is returned. 463func (fb *FileBuilder) GetEnum(name string) *EnumBuilder { 464 b := fb.symbols[name] 465 if eb, ok := b.(*EnumBuilder); ok { 466 return eb 467 } else { 468 return nil 469 } 470} 471 472// RemoveEnum removes the top-level enum with the given name. If no such enum 473// exists in the file, this is a no-op. This returns the file builder, for 474// method chaining. 475func (fb *FileBuilder) RemoveEnum(name string) *FileBuilder { 476 fb.TryRemoveEnum(name) 477 return fb 478} 479 480// TryRemoveEnum removes the top-level enum with the given name and returns 481// false if the file has no such enum. 482func (fb *FileBuilder) TryRemoveEnum(name string) bool { 483 b := fb.symbols[name] 484 if eb, ok := b.(*EnumBuilder); ok { 485 fb.removeChild(eb) 486 return true 487 } 488 return false 489} 490 491// AddEnum adds the given enum to this file. If an error prevents the enum from 492// being added, this method panics. This returns the file builder, for method 493// chaining. 494func (fb *FileBuilder) AddEnum(eb *EnumBuilder) *FileBuilder { 495 if err := fb.TryAddEnum(eb); err != nil { 496 panic(err) 497 } 498 return fb 499} 500 501// TryAddEnum adds the given enum to this file, returning any error that 502// prevents the enum from being added (such as a name collision with another 503// element already added to the file). 504func (fb *FileBuilder) TryAddEnum(eb *EnumBuilder) error { 505 if err := fb.addSymbol(eb); err != nil { 506 return err 507 } 508 Unlink(eb) 509 eb.setParent(fb) 510 fb.enums = append(fb.enums, eb) 511 return nil 512} 513 514// GetService returns the top-level service with the given name. If no such 515// service exists in the file, nil is returned. 516func (fb *FileBuilder) GetService(name string) *ServiceBuilder { 517 b := fb.symbols[name] 518 if sb, ok := b.(*ServiceBuilder); ok { 519 return sb 520 } else { 521 return nil 522 } 523} 524 525// RemoveService removes the top-level service with the given name. If no such 526// service exists in the file, this is a no-op. This returns the file builder, 527// for method chaining. 528func (fb *FileBuilder) RemoveService(name string) *FileBuilder { 529 fb.TryRemoveService(name) 530 return fb 531} 532 533// TryRemoveService removes the top-level service with the given name and 534// returns false if the file has no such service. 535func (fb *FileBuilder) TryRemoveService(name string) bool { 536 b := fb.symbols[name] 537 if sb, ok := b.(*ServiceBuilder); ok { 538 fb.removeChild(sb) 539 return true 540 } 541 return false 542} 543 544// AddService adds the given service to this file. If an error prevents the 545// service from being added, this method panics. This returns the file builder, 546// for method chaining. 547func (fb *FileBuilder) AddService(sb *ServiceBuilder) *FileBuilder { 548 if err := fb.TryAddService(sb); err != nil { 549 panic(err) 550 } 551 return fb 552} 553 554// TryAddService adds the given service to this file, returning any error that 555// prevents the service from being added (such as a name collision with another 556// element already added to the file). 557func (fb *FileBuilder) TryAddService(sb *ServiceBuilder) error { 558 if err := fb.addSymbol(sb); err != nil { 559 return err 560 } 561 Unlink(sb) 562 sb.setParent(fb) 563 fb.services = append(fb.services, sb) 564 return nil 565} 566 567// AddDependency adds the given file as an explicit import. Normally, 568// dependencies can be inferred during the build process by finding the files 569// for all referenced types (such as message and enum types used in this file). 570// However, this does not work for custom options, which must be known in order 571// to be interpretable. And they aren't known unless an explicit import is added 572// for the file that contains the custom options. 573// 574// Knowledge of custom options can also be provided by using BuildOptions with 575// an ExtensionRegistry, when building the file. 576func (fb *FileBuilder) AddDependency(dep *FileBuilder) *FileBuilder { 577 if fb.explicitDeps == nil { 578 fb.explicitDeps = map[*FileBuilder]struct{}{} 579 } 580 fb.explicitDeps[dep] = struct{}{} 581 return fb 582} 583 584// AddImportedDependency adds the given file as an explicit import. Normally, 585// dependencies can be inferred during the build process by finding the files 586// for all referenced types (such as message and enum types used in this file). 587// However, this does not work for custom options, which must be known in order 588// to be interpretable. And they aren't known unless an explicit import is added 589// for the file that contains the custom options. 590// 591// Knowledge of custom options can also be provided by using BuildOptions with 592// an ExtensionRegistry, when building the file. 593func (fb *FileBuilder) AddImportedDependency(dep *desc.FileDescriptor) *FileBuilder { 594 if fb.explicitImports == nil { 595 fb.explicitImports = map[*desc.FileDescriptor]struct{}{} 596 } 597 fb.explicitImports[dep] = struct{}{} 598 return fb 599} 600 601// SetOptions sets the file options for this file and returns the file, for 602// method chaining. 603func (fb *FileBuilder) SetOptions(options *dpb.FileOptions) *FileBuilder { 604 fb.Options = options 605 return fb 606} 607 608// SetPackageName sets the name of the package for this file and returns the 609// file, for method chaining. 610func (fb *FileBuilder) SetPackageName(pkg string) *FileBuilder { 611 fb.Package = pkg 612 return fb 613} 614 615// SetProto3 sets whether this file is declared to use "proto3" syntax or not 616// and returns the file, for method chaining. 617func (fb *FileBuilder) SetProto3(isProto3 bool) *FileBuilder { 618 fb.IsProto3 = isProto3 619 return fb 620} 621 622func (fb *FileBuilder) buildProto(deps []*desc.FileDescriptor) (*dpb.FileDescriptorProto, error) { 623 name := fb.name 624 if name == "" { 625 name = uniqueFileName() 626 } 627 var syntax *string 628 if fb.IsProto3 { 629 syntax = proto.String("proto3") 630 } 631 var pkg *string 632 if fb.Package != "" { 633 pkg = proto.String(fb.Package) 634 } 635 636 path := make([]int32, 0, 10) 637 sourceInfo := dpb.SourceCodeInfo{} 638 addCommentsTo(&sourceInfo, path, &fb.comments) 639 addCommentsTo(&sourceInfo, append(path, internal.File_syntaxTag), &fb.SyntaxComments) 640 addCommentsTo(&sourceInfo, append(path, internal.File_packageTag), &fb.PackageComments) 641 642 imports := make([]string, 0, len(deps)) 643 for _, dep := range deps { 644 imports = append(imports, dep.GetName()) 645 } 646 sort.Strings(imports) 647 648 messages := make([]*dpb.DescriptorProto, 0, len(fb.messages)) 649 for _, mb := range fb.messages { 650 path := append(path, internal.File_messagesTag, int32(len(messages))) 651 if md, err := mb.buildProto(path, &sourceInfo); err != nil { 652 return nil, err 653 } else { 654 messages = append(messages, md) 655 } 656 } 657 658 enums := make([]*dpb.EnumDescriptorProto, 0, len(fb.enums)) 659 for _, eb := range fb.enums { 660 path := append(path, internal.File_enumsTag, int32(len(enums))) 661 if ed, err := eb.buildProto(path, &sourceInfo); err != nil { 662 return nil, err 663 } else { 664 enums = append(enums, ed) 665 } 666 } 667 668 extensions := make([]*dpb.FieldDescriptorProto, 0, len(fb.extensions)) 669 for _, exb := range fb.extensions { 670 path := append(path, internal.File_extensionsTag, int32(len(extensions))) 671 if exd, err := exb.buildProto(path, &sourceInfo); err != nil { 672 return nil, err 673 } else { 674 extensions = append(extensions, exd) 675 } 676 } 677 678 services := make([]*dpb.ServiceDescriptorProto, 0, len(fb.services)) 679 for _, sb := range fb.services { 680 path := append(path, internal.File_servicesTag, int32(len(services))) 681 if sd, err := sb.buildProto(path, &sourceInfo); err != nil { 682 return nil, err 683 } else { 684 services = append(services, sd) 685 } 686 } 687 688 return &dpb.FileDescriptorProto{ 689 Name: proto.String(name), 690 Package: pkg, 691 Dependency: imports, 692 Options: fb.Options, 693 Syntax: syntax, 694 MessageType: messages, 695 EnumType: enums, 696 Extension: extensions, 697 Service: services, 698 SourceCodeInfo: &sourceInfo, 699 }, nil 700} 701 702// Build constructs a file descriptor based on the contents of this file 703// builder. If there are any problems constructing the descriptor, including 704// resolving symbols referenced by the builder or failing to meet certain 705// validation rules, an error is returned. 706func (fb *FileBuilder) Build() (*desc.FileDescriptor, error) { 707 fd, err := fb.BuildDescriptor() 708 if err != nil { 709 return nil, err 710 } 711 return fd.(*desc.FileDescriptor), nil 712} 713 714// BuildDescriptor constructs a file descriptor based on the contents of this 715// file builder. Most usages will prefer Build() instead, whose return type is a 716// concrete descriptor type. This method is present to satisfy the Builder 717// interface. 718func (fb *FileBuilder) BuildDescriptor() (desc.Descriptor, error) { 719 return doBuild(fb, BuilderOptions{}) 720} 721