github.com/phpstudyer/protoreflect@v1.7.2/desc/imports.go (about) 1 package desc 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "reflect" 7 "strings" 8 "sync" 9 10 "github.com/golang/protobuf/proto" 11 dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" 12 ) 13 14 var ( 15 globalImportPathConf map[string]string 16 globalImportPathMu sync.RWMutex 17 ) 18 19 // RegisterImportPath registers an alternate import path for a given registered 20 // proto file path. For more details on why alternate import paths may need to 21 // be configured, see ImportResolver. 22 // 23 // This method panics if provided invalid input. An empty importPath is invalid. 24 // An un-registered registerPath is also invalid. For example, if an attempt is 25 // made to register the import path "foo/bar.proto" as "bar.proto", but there is 26 // no "bar.proto" registered in the Go protobuf runtime, this method will panic. 27 // This method also panics if an attempt is made to register the same import 28 // path more than once. 29 // 30 // This function works globally, applying to all descriptors loaded by this 31 // package. If you instead want more granular support for handling alternate 32 // import paths -- such as for a single invocation of a function in this 33 // package or when the alternate path is only used from one file (so you don't 34 // want the alternate path used when loading every other file), use an 35 // ImportResolver instead. 36 func RegisterImportPath(registerPath, importPath string) { 37 if len(importPath) == 0 { 38 panic("import path cannot be empty") 39 } 40 desc := proto.FileDescriptor(registerPath) 41 if len(desc) == 0 { 42 panic(fmt.Sprintf("path %q is not a registered proto file", registerPath)) 43 } 44 globalImportPathMu.Lock() 45 defer globalImportPathMu.Unlock() 46 if reg := globalImportPathConf[importPath]; reg != "" { 47 panic(fmt.Sprintf("import path %q already registered for %s", importPath, reg)) 48 } 49 if globalImportPathConf == nil { 50 globalImportPathConf = map[string]string{} 51 } 52 globalImportPathConf[importPath] = registerPath 53 } 54 55 // ResolveImport resolves the given import path. If it has been registered as an 56 // alternate via RegisterImportPath, the registered path is returned. Otherwise, 57 // the given import path is returned unchanged. 58 func ResolveImport(importPath string) string { 59 importPath = clean(importPath) 60 globalImportPathMu.RLock() 61 defer globalImportPathMu.RUnlock() 62 reg := globalImportPathConf[importPath] 63 if reg == "" { 64 return importPath 65 } 66 return reg 67 } 68 69 // ImportResolver lets you work-around linking issues that are caused by 70 // mismatches between how a particular proto source file is registered in the Go 71 // protobuf runtime and how that same file is imported by other files. The file 72 // is registered using the same relative path given to protoc when the file is 73 // compiled (i.e. when Go code is generated). So if any file tries to import 74 // that source file, but using a different relative path, then a link error will 75 // occur when this package tries to load a descriptor for the importing file. 76 // 77 // For example, let's say we have two proto source files: "foo/bar.proto" and 78 // "fubar/baz.proto". The latter imports the former using a line like so: 79 // import "foo/bar.proto"; 80 // However, when protoc is invoked, the command-line args looks like so: 81 // protoc -Ifoo/ --go_out=foo/ bar.proto 82 // protoc -I./ -Ifubar/ --go_out=fubar/ baz.proto 83 // Because the path given to protoc is just "bar.proto" and "baz.proto", this is 84 // how they are registered in the Go protobuf runtime. So, when loading the 85 // descriptor for "fubar/baz.proto", we'll see an import path of "foo/bar.proto" 86 // but will find no file registered with that path: 87 // fd, err := desc.LoadFileDescriptor("baz.proto") 88 // // err will be non-nil, complaining that there is no such file 89 // // found named "foo/bar.proto" 90 // 91 // This can be remedied by registering alternate import paths using an 92 // ImportResolver. Continuing with the example above, the code below would fix 93 // any link issue: 94 // var r desc.ImportResolver 95 // r.RegisterImportPath("bar.proto", "foo/bar.proto") 96 // fd, err := r.LoadFileDescriptor("baz.proto") 97 // // err will be nil; descriptor successfully loaded! 98 // 99 // If there are files that are *always* imported using a different relative 100 // path then how they are registered, consider using the global 101 // RegisterImportPath function, so you don't have to use an ImportResolver for 102 // every file that imports it. 103 type ImportResolver struct { 104 children map[string]*ImportResolver 105 importPaths map[string]string 106 107 // By default, an ImportResolver will fallback to consulting any paths 108 // registered via the top-level RegisterImportPath function. Setting this 109 // field to true will cause the ImportResolver to skip that fallback and 110 // only examine its own locally registered paths. 111 SkipFallbackRules bool 112 } 113 114 // ResolveImport resolves the given import path in the context of the given 115 // source file. If a matching alternate has been registered with this resolver 116 // via a call to RegisterImportPath or RegisterImportPathFrom, then the 117 // registered path is returned. Otherwise, the given import path is returned 118 // unchanged. 119 func (r *ImportResolver) ResolveImport(source, importPath string) string { 120 if r != nil { 121 res := r.resolveImport(clean(source), clean(importPath)) 122 if res != "" { 123 return res 124 } 125 if r.SkipFallbackRules { 126 return importPath 127 } 128 } 129 return ResolveImport(importPath) 130 } 131 132 func (r *ImportResolver) resolveImport(source, importPath string) string { 133 if source == "" { 134 return r.importPaths[importPath] 135 } 136 var car, cdr string 137 idx := strings.IndexRune(source, filepath.Separator) 138 if idx < 0 { 139 car, cdr = source, "" 140 } else { 141 car, cdr = source[:idx], source[idx+1:] 142 } 143 ch := r.children[car] 144 if ch != nil { 145 if reg := ch.resolveImport(cdr, importPath); reg != "" { 146 return reg 147 } 148 } 149 return r.importPaths[importPath] 150 } 151 152 // RegisterImportPath registers an alternate import path for a given registered 153 // proto file path with this resolver. Any appearance of the given import path 154 // when linking files will instead try to link the given registered path. If the 155 // registered path cannot be located, then linking will fallback to the actual 156 // imported path. 157 // 158 // This method will panic if given an empty path or if the same import path is 159 // registered more than once. 160 // 161 // To constrain the contexts where the given import path is to be re-written, 162 // use RegisterImportPathFrom instead. 163 func (r *ImportResolver) RegisterImportPath(registerPath, importPath string) { 164 r.RegisterImportPathFrom(registerPath, importPath, "") 165 } 166 167 // RegisterImportPathFrom registers an alternate import path for a given 168 // registered proto file path with this resolver, but only for imports in the 169 // specified source context. 170 // 171 // The source context can be the name of a folder or a proto source file. Any 172 // appearance of the given import path in that context will instead try to link 173 // the given registered path. To be in context, the file that is being linked 174 // (i.e. the one whose import statement is being resolved) must be the same 175 // relative path of the source context or be a sub-path (i.e. a descendant of 176 // the source folder). 177 // 178 // If the registered path cannot be located, then linking will fallback to the 179 // actual imported path. 180 // 181 // This method will panic if given an empty path. The source context, on the 182 // other hand, is allowed to be blank. A blank source matches all files. This 183 // method also panics if the same import path is registered in the same source 184 // context more than once. 185 func (r *ImportResolver) RegisterImportPathFrom(registerPath, importPath, source string) { 186 importPath = clean(importPath) 187 if len(importPath) == 0 { 188 panic("import path cannot be empty") 189 } 190 registerPath = clean(registerPath) 191 if len(registerPath) == 0 { 192 panic("registered path cannot be empty") 193 } 194 r.registerImportPathFrom(registerPath, importPath, clean(source)) 195 } 196 197 func (r *ImportResolver) registerImportPathFrom(registerPath, importPath, source string) { 198 if source == "" { 199 if r.importPaths == nil { 200 r.importPaths = map[string]string{} 201 } else if reg := r.importPaths[importPath]; reg != "" { 202 panic(fmt.Sprintf("already registered import path %q as %q", importPath, registerPath)) 203 } 204 r.importPaths[importPath] = registerPath 205 return 206 } 207 var car, cdr string 208 idx := strings.IndexRune(source, filepath.Separator) 209 if idx < 0 { 210 car, cdr = source, "" 211 } else { 212 car, cdr = source[:idx], source[idx+1:] 213 } 214 ch := r.children[car] 215 if ch == nil { 216 if r.children == nil { 217 r.children = map[string]*ImportResolver{} 218 } 219 ch = &ImportResolver{} 220 r.children[car] = ch 221 } 222 ch.registerImportPathFrom(registerPath, importPath, cdr) 223 } 224 225 // LoadFileDescriptor is the same as the package function of the same name, but 226 // any alternate paths configured in this resolver are used when linking the 227 // given descriptor proto. 228 func (r *ImportResolver) LoadFileDescriptor(filePath string) (*FileDescriptor, error) { 229 return loadFileDescriptor(filePath, r) 230 } 231 232 // LoadMessageDescriptor is the same as the package function of the same name, 233 // but any alternate paths configured in this resolver are used when linking 234 // files for the returned descriptor. 235 func (r *ImportResolver) LoadMessageDescriptor(msgName string) (*MessageDescriptor, error) { 236 return loadMessageDescriptor(msgName, r) 237 } 238 239 // LoadMessageDescriptorForMessage is the same as the package function of the 240 // same name, but any alternate paths configured in this resolver are used when 241 // linking files for the returned descriptor. 242 func (r *ImportResolver) LoadMessageDescriptorForMessage(msg proto.Message) (*MessageDescriptor, error) { 243 return loadMessageDescriptorForMessage(msg, r) 244 } 245 246 // LoadMessageDescriptorForType is the same as the package function of the same 247 // name, but any alternate paths configured in this resolver are used when 248 // linking files for the returned descriptor. 249 func (r *ImportResolver) LoadMessageDescriptorForType(msgType reflect.Type) (*MessageDescriptor, error) { 250 return loadMessageDescriptorForType(msgType, r) 251 } 252 253 // LoadEnumDescriptorForEnum is the same as the package function of the same 254 // name, but any alternate paths configured in this resolver are used when 255 // linking files for the returned descriptor. 256 func (r *ImportResolver) LoadEnumDescriptorForEnum(enum protoEnum) (*EnumDescriptor, error) { 257 return loadEnumDescriptorForEnum(enum, r) 258 } 259 260 // LoadEnumDescriptorForType is the same as the package function of the same 261 // name, but any alternate paths configured in this resolver are used when 262 // linking files for the returned descriptor. 263 func (r *ImportResolver) LoadEnumDescriptorForType(enumType reflect.Type) (*EnumDescriptor, error) { 264 return loadEnumDescriptorForType(enumType, r) 265 } 266 267 // LoadFieldDescriptorForExtension is the same as the package function of the 268 // same name, but any alternate paths configured in this resolver are used when 269 // linking files for the returned descriptor. 270 func (r *ImportResolver) LoadFieldDescriptorForExtension(ext *proto.ExtensionDesc) (*FieldDescriptor, error) { 271 return loadFieldDescriptorForExtension(ext, r) 272 } 273 274 // CreateFileDescriptor is the same as the package function of the same name, 275 // but any alternate paths configured in this resolver are used when linking the 276 // given descriptor proto. 277 func (r *ImportResolver) CreateFileDescriptor(fdp *dpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) { 278 return createFileDescriptor(fdp, deps, r) 279 } 280 281 // CreateFileDescriptors is the same as the package function of the same name, 282 // but any alternate paths configured in this resolver are used when linking the 283 // given descriptor protos. 284 func (r *ImportResolver) CreateFileDescriptors(fds []*dpb.FileDescriptorProto) (map[string]*FileDescriptor, error) { 285 return createFileDescriptors(fds, r) 286 } 287 288 // CreateFileDescriptorFromSet is the same as the package function of the same 289 // name, but any alternate paths configured in this resolver are used when 290 // linking the descriptor protos in the given set. 291 func (r *ImportResolver) CreateFileDescriptorFromSet(fds *dpb.FileDescriptorSet) (*FileDescriptor, error) { 292 return createFileDescriptorFromSet(fds, r) 293 } 294 295 // CreateFileDescriptorsFromSet is the same as the package function of the same 296 // name, but any alternate paths configured in this resolver are used when 297 // linking the descriptor protos in the given set. 298 func (r *ImportResolver) CreateFileDescriptorsFromSet(fds *dpb.FileDescriptorSet) (map[string]*FileDescriptor, error) { 299 return createFileDescriptorsFromSet(fds, r) 300 } 301 302 const dotPrefix = "." + string(filepath.Separator) 303 304 func clean(path string) string { 305 if path == "" { 306 return "" 307 } 308 path = filepath.Clean(path) 309 if path == "." { 310 return "" 311 } 312 return strings.TrimPrefix(path, dotPrefix) 313 }