| package desc |
| |
| import ( |
| "fmt" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "sync" |
| |
| "github.com/golang/protobuf/proto" |
| dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" |
| ) |
| |
| var ( |
| globalImportPathConf map[string]string |
| globalImportPathMu sync.RWMutex |
| ) |
| |
| // RegisterImportPath registers an alternate import path for a given registered |
| // proto file path. For more details on why alternate import paths may need to |
| // be configured, see ImportResolver. |
| // |
| // This method panics if provided invalid input. An empty importPath is invalid. |
| // An un-registered registerPath is also invalid. For example, if an attempt is |
| // made to register the import path "foo/bar.proto" as "bar.proto", but there is |
| // no "bar.proto" registered in the Go protobuf runtime, this method will panic. |
| // This method also panics if an attempt is made to register the same import |
| // path more than once. |
| // |
| // This function works globally, applying to all descriptors loaded by this |
| // package. If you instead want more granular support for handling alternate |
| // import paths -- such as for a single invocation of a function in this |
| // package or when the alternate path is only used from one file (so you don't |
| // want the alternate path used when loading every other file), use an |
| // ImportResolver instead. |
| func RegisterImportPath(registerPath, importPath string) { |
| if len(importPath) == 0 { |
| panic("import path cannot be empty") |
| } |
| desc := proto.FileDescriptor(registerPath) |
| if len(desc) == 0 { |
| panic(fmt.Sprintf("path %q is not a registered proto file", registerPath)) |
| } |
| globalImportPathMu.Lock() |
| defer globalImportPathMu.Unlock() |
| if reg := globalImportPathConf[importPath]; reg != "" { |
| panic(fmt.Sprintf("import path %q already registered for %s", importPath, reg)) |
| } |
| if globalImportPathConf == nil { |
| globalImportPathConf = map[string]string{} |
| } |
| globalImportPathConf[importPath] = registerPath |
| } |
| |
| // ResolveImport resolves the given import path. If it has been registered as an |
| // alternate via RegisterImportPath, the registered path is returned. Otherwise, |
| // the given import path is returned unchanged. |
| func ResolveImport(importPath string) string { |
| importPath = clean(importPath) |
| globalImportPathMu.RLock() |
| defer globalImportPathMu.RUnlock() |
| reg := globalImportPathConf[importPath] |
| if reg == "" { |
| return importPath |
| } |
| return reg |
| } |
| |
| // ImportResolver lets you work-around linking issues that are caused by |
| // mismatches between how a particular proto source file is registered in the Go |
| // protobuf runtime and how that same file is imported by other files. The file |
| // is registered using the same relative path given to protoc when the file is |
| // compiled (i.e. when Go code is generated). So if any file tries to import |
| // that source file, but using a different relative path, then a link error will |
| // occur when this package tries to load a descriptor for the importing file. |
| // |
| // For example, let's say we have two proto source files: "foo/bar.proto" and |
| // "fubar/baz.proto". The latter imports the former using a line like so: |
| // import "foo/bar.proto"; |
| // However, when protoc is invoked, the command-line args looks like so: |
| // protoc -Ifoo/ --go_out=foo/ bar.proto |
| // protoc -I./ -Ifubar/ --go_out=fubar/ baz.proto |
| // Because the path given to protoc is just "bar.proto" and "baz.proto", this is |
| // how they are registered in the Go protobuf runtime. So, when loading the |
| // descriptor for "fubar/baz.proto", we'll see an import path of "foo/bar.proto" |
| // but will find no file registered with that path: |
| // fd, err := desc.LoadFileDescriptor("baz.proto") |
| // // err will be non-nil, complaining that there is no such file |
| // // found named "foo/bar.proto" |
| // |
| // This can be remedied by registering alternate import paths using an |
| // ImportResolver. Continuing with the example above, the code below would fix |
| // any link issue: |
| // var r desc.ImportResolver |
| // r.RegisterImportPath("bar.proto", "foo/bar.proto") |
| // fd, err := r.LoadFileDescriptor("baz.proto") |
| // // err will be nil; descriptor successfully loaded! |
| // |
| // If there are files that are *always* imported using a different relative |
| // path then how they are registered, consider using the global |
| // RegisterImportPath function, so you don't have to use an ImportResolver for |
| // every file that imports it. |
| type ImportResolver struct { |
| children map[string]*ImportResolver |
| importPaths map[string]string |
| |
| // By default, an ImportResolver will fallback to consulting any paths |
| // registered via the top-level RegisterImportPath function. Setting this |
| // field to true will cause the ImportResolver to skip that fallback and |
| // only examine its own locally registered paths. |
| SkipFallbackRules bool |
| } |
| |
| // ResolveImport resolves the given import path in the context of the given |
| // source file. If a matching alternate has been registered with this resolver |
| // via a call to RegisterImportPath or RegisterImportPathFrom, then the |
| // registered path is returned. Otherwise, the given import path is returned |
| // unchanged. |
| func (r *ImportResolver) ResolveImport(source, importPath string) string { |
| if r != nil { |
| res := r.resolveImport(clean(source), clean(importPath)) |
| if res != "" { |
| return res |
| } |
| if r.SkipFallbackRules { |
| return importPath |
| } |
| } |
| return ResolveImport(importPath) |
| } |
| |
| func (r *ImportResolver) resolveImport(source, importPath string) string { |
| if source == "" { |
| return r.importPaths[importPath] |
| } |
| var car, cdr string |
| idx := strings.IndexRune(source, filepath.Separator) |
| if idx < 0 { |
| car, cdr = source, "" |
| } else { |
| car, cdr = source[:idx], source[idx+1:] |
| } |
| ch := r.children[car] |
| if ch != nil { |
| if reg := ch.resolveImport(cdr, importPath); reg != "" { |
| return reg |
| } |
| } |
| return r.importPaths[importPath] |
| } |
| |
| // RegisterImportPath registers an alternate import path for a given registered |
| // proto file path with this resolver. Any appearance of the given import path |
| // when linking files will instead try to link the given registered path. If the |
| // registered path cannot be located, then linking will fallback to the actual |
| // imported path. |
| // |
| // This method will panic if given an empty path or if the same import path is |
| // registered more than once. |
| // |
| // To constrain the contexts where the given import path is to be re-written, |
| // use RegisterImportPathFrom instead. |
| func (r *ImportResolver) RegisterImportPath(registerPath, importPath string) { |
| r.RegisterImportPathFrom(registerPath, importPath, "") |
| } |
| |
| // RegisterImportPathFrom registers an alternate import path for a given |
| // registered proto file path with this resolver, but only for imports in the |
| // specified source context. |
| // |
| // The source context can be the name of a folder or a proto source file. Any |
| // appearance of the given import path in that context will instead try to link |
| // the given registered path. To be in context, the file that is being linked |
| // (i.e. the one whose import statement is being resolved) must be the same |
| // relative path of the source context or be a sub-path (i.e. a descendant of |
| // the source folder). |
| // |
| // If the registered path cannot be located, then linking will fallback to the |
| // actual imported path. |
| // |
| // This method will panic if given an empty path. The source context, on the |
| // other hand, is allowed to be blank. A blank source matches all files. This |
| // method also panics if the same import path is registered in the same source |
| // context more than once. |
| func (r *ImportResolver) RegisterImportPathFrom(registerPath, importPath, source string) { |
| importPath = clean(importPath) |
| if len(importPath) == 0 { |
| panic("import path cannot be empty") |
| } |
| registerPath = clean(registerPath) |
| if len(registerPath) == 0 { |
| panic("registered path cannot be empty") |
| } |
| r.registerImportPathFrom(registerPath, importPath, clean(source)) |
| } |
| |
| func (r *ImportResolver) registerImportPathFrom(registerPath, importPath, source string) { |
| if source == "" { |
| if r.importPaths == nil { |
| r.importPaths = map[string]string{} |
| } else if reg := r.importPaths[importPath]; reg != "" { |
| panic(fmt.Sprintf("already registered import path %q as %q", importPath, registerPath)) |
| } |
| r.importPaths[importPath] = registerPath |
| return |
| } |
| var car, cdr string |
| idx := strings.IndexRune(source, filepath.Separator) |
| if idx < 0 { |
| car, cdr = source, "" |
| } else { |
| car, cdr = source[:idx], source[idx+1:] |
| } |
| ch := r.children[car] |
| if ch == nil { |
| if r.children == nil { |
| r.children = map[string]*ImportResolver{} |
| } |
| ch = &ImportResolver{} |
| r.children[car] = ch |
| } |
| ch.registerImportPathFrom(registerPath, importPath, cdr) |
| } |
| |
| // LoadFileDescriptor is the same as the package function of the same name, but |
| // any alternate paths configured in this resolver are used when linking the |
| // given descriptor proto. |
| func (r *ImportResolver) LoadFileDescriptor(filePath string) (*FileDescriptor, error) { |
| return loadFileDescriptor(filePath, r) |
| } |
| |
| // LoadMessageDescriptor is the same as the package function of the same name, |
| // but any alternate paths configured in this resolver are used when linking |
| // files for the returned descriptor. |
| func (r *ImportResolver) LoadMessageDescriptor(msgName string) (*MessageDescriptor, error) { |
| return loadMessageDescriptor(msgName, r) |
| } |
| |
| // LoadMessageDescriptorForMessage is the same as the package function of the |
| // same name, but any alternate paths configured in this resolver are used when |
| // linking files for the returned descriptor. |
| func (r *ImportResolver) LoadMessageDescriptorForMessage(msg proto.Message) (*MessageDescriptor, error) { |
| return loadMessageDescriptorForMessage(msg, r) |
| } |
| |
| // LoadMessageDescriptorForType is the same as the package function of the same |
| // name, but any alternate paths configured in this resolver are used when |
| // linking files for the returned descriptor. |
| func (r *ImportResolver) LoadMessageDescriptorForType(msgType reflect.Type) (*MessageDescriptor, error) { |
| return loadMessageDescriptorForType(msgType, r) |
| } |
| |
| // LoadEnumDescriptorForEnum is the same as the package function of the same |
| // name, but any alternate paths configured in this resolver are used when |
| // linking files for the returned descriptor. |
| func (r *ImportResolver) LoadEnumDescriptorForEnum(enum protoEnum) (*EnumDescriptor, error) { |
| return loadEnumDescriptorForEnum(enum, r) |
| } |
| |
| // LoadEnumDescriptorForType is the same as the package function of the same |
| // name, but any alternate paths configured in this resolver are used when |
| // linking files for the returned descriptor. |
| func (r *ImportResolver) LoadEnumDescriptorForType(enumType reflect.Type) (*EnumDescriptor, error) { |
| return loadEnumDescriptorForType(enumType, r) |
| } |
| |
| // LoadFieldDescriptorForExtension is the same as the package function of the |
| // same name, but any alternate paths configured in this resolver are used when |
| // linking files for the returned descriptor. |
| func (r *ImportResolver) LoadFieldDescriptorForExtension(ext *proto.ExtensionDesc) (*FieldDescriptor, error) { |
| return loadFieldDescriptorForExtension(ext, r) |
| } |
| |
| // CreateFileDescriptor is the same as the package function of the same name, |
| // but any alternate paths configured in this resolver are used when linking the |
| // given descriptor proto. |
| func (r *ImportResolver) CreateFileDescriptor(fdp *dpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) { |
| return createFileDescriptor(fdp, deps, r) |
| } |
| |
| // CreateFileDescriptors is the same as the package function of the same name, |
| // but any alternate paths configured in this resolver are used when linking the |
| // given descriptor protos. |
| func (r *ImportResolver) CreateFileDescriptors(fds []*dpb.FileDescriptorProto) (map[string]*FileDescriptor, error) { |
| return createFileDescriptors(fds, r) |
| } |
| |
| // CreateFileDescriptorFromSet is the same as the package function of the same |
| // name, but any alternate paths configured in this resolver are used when |
| // linking the descriptor protos in the given set. |
| func (r *ImportResolver) CreateFileDescriptorFromSet(fds *dpb.FileDescriptorSet) (*FileDescriptor, error) { |
| return createFileDescriptorFromSet(fds, r) |
| } |
| |
| // CreateFileDescriptorsFromSet is the same as the package function of the same |
| // name, but any alternate paths configured in this resolver are used when |
| // linking the descriptor protos in the given set. |
| func (r *ImportResolver) CreateFileDescriptorsFromSet(fds *dpb.FileDescriptorSet) (map[string]*FileDescriptor, error) { |
| return createFileDescriptorsFromSet(fds, r) |
| } |
| |
| const dotPrefix = "." + string(filepath.Separator) |
| |
| func clean(path string) string { |
| if path == "" { |
| return "" |
| } |
| path = filepath.Clean(path) |
| if path == "." { |
| return "" |
| } |
| return strings.TrimPrefix(path, dotPrefix) |
| } |