232 lines
7.9 KiB
Go
232 lines
7.9 KiB
Go
|
package desc
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||
|
|
||
|
"github.com/jhump/protoreflect/desc/internal"
|
||
|
intn "github.com/jhump/protoreflect/internal"
|
||
|
)
|
||
|
|
||
|
// CreateFileDescriptor instantiates a new file descriptor for the given descriptor proto.
|
||
|
// The file's direct dependencies must be provided. If the given dependencies do not include
|
||
|
// all of the file's dependencies or if the contents of the descriptors are internally
|
||
|
// inconsistent (e.g. contain unresolvable symbols) then an error is returned.
|
||
|
func CreateFileDescriptor(fd *dpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) {
|
||
|
return createFileDescriptor(fd, deps, nil)
|
||
|
}
|
||
|
|
||
|
func createFileDescriptor(fd *dpb.FileDescriptorProto, deps []*FileDescriptor, r *ImportResolver) (*FileDescriptor, error) {
|
||
|
ret := &FileDescriptor{
|
||
|
proto: fd,
|
||
|
symbols: map[string]Descriptor{},
|
||
|
fieldIndex: map[string]map[int32]*FieldDescriptor{},
|
||
|
}
|
||
|
pkg := fd.GetPackage()
|
||
|
|
||
|
// populate references to file descriptor dependencies
|
||
|
files := map[string]*FileDescriptor{}
|
||
|
for _, f := range deps {
|
||
|
files[f.proto.GetName()] = f
|
||
|
}
|
||
|
ret.deps = make([]*FileDescriptor, len(fd.GetDependency()))
|
||
|
for i, d := range fd.GetDependency() {
|
||
|
resolved := r.ResolveImport(fd.GetName(), d)
|
||
|
ret.deps[i] = files[resolved]
|
||
|
if ret.deps[i] == nil {
|
||
|
if resolved != d {
|
||
|
ret.deps[i] = files[d]
|
||
|
}
|
||
|
if ret.deps[i] == nil {
|
||
|
return nil, intn.ErrNoSuchFile(d)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ret.publicDeps = make([]*FileDescriptor, len(fd.GetPublicDependency()))
|
||
|
for i, pd := range fd.GetPublicDependency() {
|
||
|
ret.publicDeps[i] = ret.deps[pd]
|
||
|
}
|
||
|
ret.weakDeps = make([]*FileDescriptor, len(fd.GetWeakDependency()))
|
||
|
for i, wd := range fd.GetWeakDependency() {
|
||
|
ret.weakDeps[i] = ret.deps[wd]
|
||
|
}
|
||
|
ret.isProto3 = fd.GetSyntax() == "proto3"
|
||
|
|
||
|
// populate all tables of child descriptors
|
||
|
for _, m := range fd.GetMessageType() {
|
||
|
md, n := createMessageDescriptor(ret, ret, pkg, m, ret.symbols)
|
||
|
ret.symbols[n] = md
|
||
|
ret.messages = append(ret.messages, md)
|
||
|
}
|
||
|
for _, e := range fd.GetEnumType() {
|
||
|
ed, n := createEnumDescriptor(ret, ret, pkg, e, ret.symbols)
|
||
|
ret.symbols[n] = ed
|
||
|
ret.enums = append(ret.enums, ed)
|
||
|
}
|
||
|
for _, ex := range fd.GetExtension() {
|
||
|
exd, n := createFieldDescriptor(ret, ret, pkg, ex)
|
||
|
ret.symbols[n] = exd
|
||
|
ret.extensions = append(ret.extensions, exd)
|
||
|
}
|
||
|
for _, s := range fd.GetService() {
|
||
|
sd, n := createServiceDescriptor(ret, pkg, s, ret.symbols)
|
||
|
ret.symbols[n] = sd
|
||
|
ret.services = append(ret.services, sd)
|
||
|
}
|
||
|
|
||
|
ret.sourceInfo = internal.CreateSourceInfoMap(fd)
|
||
|
ret.sourceInfoRecomputeFunc = ret.recomputeSourceInfo
|
||
|
|
||
|
// now we can resolve all type references and source code info
|
||
|
scopes := []scope{fileScope(ret)}
|
||
|
path := make([]int32, 1, 8)
|
||
|
path[0] = internal.File_messagesTag
|
||
|
for i, md := range ret.messages {
|
||
|
if err := md.resolve(append(path, int32(i)), scopes); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
path[0] = internal.File_enumsTag
|
||
|
for i, ed := range ret.enums {
|
||
|
ed.resolve(append(path, int32(i)))
|
||
|
}
|
||
|
path[0] = internal.File_extensionsTag
|
||
|
for i, exd := range ret.extensions {
|
||
|
if err := exd.resolve(append(path, int32(i)), scopes); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
path[0] = internal.File_servicesTag
|
||
|
for i, sd := range ret.services {
|
||
|
if err := sd.resolve(append(path, int32(i)), scopes); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret, nil
|
||
|
}
|
||
|
|
||
|
// CreateFileDescriptors constructs a set of descriptors, one for each of the
|
||
|
// given descriptor protos. The given set of descriptor protos must include all
|
||
|
// transitive dependencies for every file.
|
||
|
func CreateFileDescriptors(fds []*dpb.FileDescriptorProto) (map[string]*FileDescriptor, error) {
|
||
|
return createFileDescriptors(fds, nil)
|
||
|
}
|
||
|
|
||
|
func createFileDescriptors(fds []*dpb.FileDescriptorProto, r *ImportResolver) (map[string]*FileDescriptor, error) {
|
||
|
if len(fds) == 0 {
|
||
|
return nil, nil
|
||
|
}
|
||
|
files := map[string]*dpb.FileDescriptorProto{}
|
||
|
resolved := map[string]*FileDescriptor{}
|
||
|
var name string
|
||
|
for _, fd := range fds {
|
||
|
name = fd.GetName()
|
||
|
files[name] = fd
|
||
|
}
|
||
|
for _, fd := range fds {
|
||
|
_, err := createFromSet(fd.GetName(), r, nil, files, resolved)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return resolved, nil
|
||
|
}
|
||
|
|
||
|
// ToFileDescriptorSet creates a FileDescriptorSet proto that contains all of the given
|
||
|
// file descriptors and their transitive dependencies. The files are topologically sorted
|
||
|
// so that a file will always appear after its dependencies.
|
||
|
func ToFileDescriptorSet(fds ...*FileDescriptor) *dpb.FileDescriptorSet {
|
||
|
var fdps []*dpb.FileDescriptorProto
|
||
|
addAllFiles(fds, &fdps, map[string]struct{}{})
|
||
|
return &dpb.FileDescriptorSet{File: fdps}
|
||
|
}
|
||
|
|
||
|
func addAllFiles(src []*FileDescriptor, results *[]*dpb.FileDescriptorProto, seen map[string]struct{}) {
|
||
|
for _, fd := range src {
|
||
|
if _, ok := seen[fd.GetName()]; ok {
|
||
|
continue
|
||
|
}
|
||
|
seen[fd.GetName()] = struct{}{}
|
||
|
addAllFiles(fd.GetDependencies(), results, seen)
|
||
|
*results = append(*results, fd.AsFileDescriptorProto())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CreateFileDescriptorFromSet creates a descriptor from the given file descriptor set. The
|
||
|
// set's *last* file will be the returned descriptor. The set's remaining files must comprise
|
||
|
// the full set of transitive dependencies of that last file. This is the same format and
|
||
|
// order used by protoc when emitting a FileDescriptorSet file with an invocation like so:
|
||
|
// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
|
||
|
func CreateFileDescriptorFromSet(fds *dpb.FileDescriptorSet) (*FileDescriptor, error) {
|
||
|
return createFileDescriptorFromSet(fds, nil)
|
||
|
}
|
||
|
|
||
|
func createFileDescriptorFromSet(fds *dpb.FileDescriptorSet, r *ImportResolver) (*FileDescriptor, error) {
|
||
|
result, err := createFileDescriptorsFromSet(fds, r)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
files := fds.GetFile()
|
||
|
lastFilename := files[len(files)-1].GetName()
|
||
|
return result[lastFilename], nil
|
||
|
}
|
||
|
|
||
|
// CreateFileDescriptorsFromSet creates file descriptors from the given file descriptor set.
|
||
|
// The returned map includes all files in the set, keyed b name. The set must include the
|
||
|
// full set of transitive dependencies for all files therein or else a link error will occur
|
||
|
// and be returned instead of the slice of descriptors. This is the same format used by
|
||
|
// protoc when a FileDescriptorSet file with an invocation like so:
|
||
|
// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
|
||
|
func CreateFileDescriptorsFromSet(fds *dpb.FileDescriptorSet) (map[string]*FileDescriptor, error) {
|
||
|
return createFileDescriptorsFromSet(fds, nil)
|
||
|
}
|
||
|
|
||
|
func createFileDescriptorsFromSet(fds *dpb.FileDescriptorSet, r *ImportResolver) (map[string]*FileDescriptor, error) {
|
||
|
files := fds.GetFile()
|
||
|
if len(files) == 0 {
|
||
|
return nil, errors.New("file descriptor set is empty")
|
||
|
}
|
||
|
return createFileDescriptors(files, r)
|
||
|
}
|
||
|
|
||
|
// createFromSet creates a descriptor for the given filename. It recursively
|
||
|
// creates descriptors for the given file's dependencies.
|
||
|
func createFromSet(filename string, r *ImportResolver, seen []string, files map[string]*dpb.FileDescriptorProto, resolved map[string]*FileDescriptor) (*FileDescriptor, error) {
|
||
|
for _, s := range seen {
|
||
|
if filename == s {
|
||
|
return nil, fmt.Errorf("cycle in imports: %s", strings.Join(append(seen, filename), " -> "))
|
||
|
}
|
||
|
}
|
||
|
seen = append(seen, filename)
|
||
|
|
||
|
if d, ok := resolved[filename]; ok {
|
||
|
return d, nil
|
||
|
}
|
||
|
fdp := files[filename]
|
||
|
if fdp == nil {
|
||
|
return nil, intn.ErrNoSuchFile(filename)
|
||
|
}
|
||
|
deps := make([]*FileDescriptor, len(fdp.GetDependency()))
|
||
|
for i, depName := range fdp.GetDependency() {
|
||
|
resolvedDep := r.ResolveImport(filename, depName)
|
||
|
dep, err := createFromSet(resolvedDep, r, seen, files, resolved)
|
||
|
if _, ok := err.(intn.ErrNoSuchFile); ok && resolvedDep != depName {
|
||
|
dep, err = createFromSet(depName, r, seen, files, resolved)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
deps[i] = dep
|
||
|
}
|
||
|
d, err := createFileDescriptor(fdp, deps, r)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
resolved[filename] = d
|
||
|
return d, nil
|
||
|
}
|