-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to retrieve custom options inside of a compiler/protogen plugin? #1260
Comments
The import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
option "examples/options/proto"
)
options := sourceFile.Messages[0].Desc.Options().(*descriptorpb.MessageOptions)
ext1 := proto.GetExtension(options, option.E_ExampleAnnotationString).(string) BTW, |
Thanks @dsnet. I've gotten this to work if I use the generated E_ExampleAnnotationString var as you describe, but as noted in my original question I'm aiming to do this without having to recompile my protoc plugin for each new input -- my use case is a template-based generator similar to https://github.com/moul/protoc-gen-gotemplate (which uses the old protobuf go library in clearly unsupported ways) so custom options are not known at the protoc plugin's compile time. The new protobuf go library makes almost everything I need to access quite easy -- but the one piece of information in the .proto files that I don't seem to be able to access (without compiling the generated go modules into my protoc plugin as you suggest) is the value of custom options. I did another unsuccessful experiment along these lines: in order to avoid recompiling my protoc plugin I attempted passing the protogen.Message to a go plugin that was compiled with the generated go code (ie E_ExampleAnnotationString) -- a reasonable separation of concerns between a generic protoc plugin and a go plugin familiar with one project's custom options -- but the go plugin's code is not able to retrieve the custom option information out of the protogen.Message, I assume because the info on the options was not saved into the Message structure internals in the protoc plugin because the generated code's init() function didn't run in the protoc plugin (only in the go plugin it called). (thanks also for the note on my go package path -- I was indeed being lazy in putting together my example) |
I see, can you try obtaining them dynamically through reflection? options := sourceFile.Messages[0].Desc.Options().(*descriptorpb.MessageOptions)
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
continue
}
fmt.Println(fd, v)
// Make use of fd and v based on their reflective properties.
return true
}) |
Actually, I don't think the above will work since the compiled binary will not know about any dynamically created extensions that are passed at runtime. You'll need to create a {
var gen protogen.Plugin
// The type information for all extensions is in the source files,
// so we need to extract them into a dynamically created protoregistry.Types.
extTypes := new(protoregistry.Types)
for _, file := range gen.Files {
if err := registerAllExtensions(extTypes, file.Desc); err != nil {
panic(err)
}
}
for _, sourceFile := range gen.Files {
if !sourceFile.Generate {
continue
}
// The MessageOptions as provided by protoc does not know about
// dynamically created extensions, so they are left as unknown fields.
// We round-trip marshal and unmarshal the options with
// a dynamically created resolver that does know about extensions at runtime.
options := sourceFile.Messages[0].Desc.Options().(*descriptorpb.MessageOptions)
b, err := proto.Marshal(options)
if err != nil {
panic(err)
}
options.Reset()
err = proto.UnmarshalOptions{Resolver: extTypes}.Unmarshal(b, options)
if err != nil {
panic(err)
}
// Use protobuf reflection to iterate over all the extension fields,
// looking for the ones that we are interested in.
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
if !fd.IsExtension() {
return true
}
fmt.Println(fd, v)
// Make use of fd and v based on their reflective properties.
return true
})
}
}
func registerAllExtensions(extTypes *protoregistry.Types, descs interface {
Messages() protoreflect.MessageDescriptors
Extensions() protoreflect.ExtensionDescriptors
}) error {
mds := descs.Messages()
for i := 0; i < mds.Len(); i++ {
registerAllExtensions(extTypes, mds.Get(i))
}
xds := descs.Extensions()
for i := 0; i < xds.Len(); i++ {
if err := extTypes.RegisterExtension(dynamicpb.NewExtensionType(xds.Get(i))); err != nil {
return err
}
}
return nil
} |
Thanks @dsnet - very tricky! I confirmed that the simple Range() solution doesn't work as you expected, and that the Marshal-Unmarshal one does. I stuck my completed example in this repo for posterity. Many thanks! |
Great to hear! Anything left to do here? This is the first time I've seen a plugin implementation that needed to dynamically reinterpret the options based on the set of input sources it is operating on. It's cool to see that the new API is able to support this advanced use case. The old API had no hope for accomplishing this. |
I believe I'm all set, many thanks. Definitely an impressive use of the new reflection abilities. FWIW, while digging around I found two other examples while digging of people facing a similar same issue - this stackoverflow question (to which I posted your solution, a link to this issue and my repo) and the stringMethodOptionsExtension and similar methods inside the protoc-gen-gotemplate project, which appear to be attempting an awkward (given the lack of reflection) version of a similar solution to yours using the old API. |
Hi, is it possible to dynamically register field options similar to #1260 (comment)? |
hi all - I'm writing a template-based protoc plugin using the framework provided by compiler/protogen, and after a fair bit of digging and experimentation I've been unable to figure out how to retrieve the value of custom options during execution. Since I'm building a protoc plugin (which I don't want to recompile for every protobuf input) I can't use the generated go code (notably the E_* vars), and I also don't have a Message such as is used the example in the March blog post announcing the new protobuf go API
Here's as close as I've been able to come:
sample option file:
... and a simple main.go of a plugin written in go:
the output is:
While this proves the data is present in the "options" descriptorpb.MessageOptions... how can I use the API to read it? This data does not appear in the result of descriptorpb.MessageOptions.GetUninterpretedOption().
The text was updated successfully, but these errors were encountered: