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