forked from jhump/protoreflect
/
imports.go
313 lines (288 loc) · 12.4 KB
/
imports.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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)
}