github.com/Big-big-orange/protoreflect@v0.0.0-20240408141420-285cedfdf6a4/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  }