Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions protoc-gen-prost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ In addition, the following options can also be specified:
structure will be flattened, with all generated files placed directly
into the specified output directory. By default, the output directory
structure mirrors the input protobuf file paths.
* `prost_reflect`: When specified together with `file_descriptor_set`, generate
implementations of [prost_reflect::ReflectMessage](https://docs.rs/prost-reflect/latest/prost_reflect/trait.ReflectMessage.html) trait for the generated rust struct. Note that this option
depends on `file_descriptor_set`, and when enabled, the generated `FileDescriptorSet`
*will* include all the dependent protobuf files in addition to the module being generated,
which required for prost_reflect descriptor to work.
Comment on lines +60 to +64

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added documentation here. Let me know if there's other places I need to update

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is good!


A note on parameter values:

Expand Down Expand Up @@ -85,6 +90,7 @@ plugins:
- compile_well_known_types
- extern_path=.google.protobuf=::pbjson_types
- file_descriptor_set
- prost_reflect
- type_attribute=.helloworld.v1.HelloWorld=#[derive(Eq\, Hash)]
```

Expand Down
64 changes: 62 additions & 2 deletions protoc-gen-prost/src/generator/file_descriptor_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,50 @@ use prost_types::compiler::code_generator_response::File;

use crate::{Generator, ModuleRequest, ModuleRequestSet, Result};

pub struct FileDescriptorSetGenerator;
pub struct FileDescriptorSetGenerator {
/// Whether to include all dependent proto files in the FileDescriptorSet.
/// When true, the generated FileDescriptorSet will include all imported proto files
/// from all modules, which is required for prost-reflect to work correctly.
include_all_dependencies: bool,
}

impl FileDescriptorSetGenerator {
/// Creates a new FileDescriptorSetGenerator that includes only the proto files
/// in each module (not their dependencies)
pub fn new() -> Self {
Self {
include_all_dependencies: false,
}
}

/// Creates a new FileDescriptorSetGenerator that includes all dependent proto files
/// from all modules. This is required when using prost-reflect.
pub fn with_all_dependencies() -> Self {
Self {
include_all_dependencies: true,
}
}
}

impl Generator for FileDescriptorSetGenerator {
fn generate(&mut self, module_request_set: &ModuleRequestSet) -> Result {
let files = module_request_set
.requests()
.filter_map(|(_, request)| Self::generate_one(request))
.filter_map(|(_, request)| {
if self.include_all_dependencies {
Self::generate_all_dependent(request, module_request_set)
} else {
Self::generate_one(request)
}
})
.collect();

Ok(files)
}
}

impl FileDescriptorSetGenerator {
/// Generates a FileDescriptorSet containing only the proto files in the current module
fn generate_one(request: &ModuleRequest) -> Option<File> {
request.append_to_file(|buffer| {
// This cannot be done with another file and `include_bytes!` because the
Expand All @@ -32,6 +63,35 @@ impl FileDescriptorSetGenerator {
);
})
}

/// Generates a FileDescriptorSet containing all proto files from all modules.
/// This ensures imported dependencies are included, which is required for prost-reflect.
fn generate_all_dependent(
request: &ModuleRequest,
module_request_set: &ModuleRequestSet,
) -> Option<File> {
request.append_to_file(|buffer| {
// This cannot be done with another file and `include_bytes!` because the
// contract for a file's contents requires that they be valid UTF-8.
//
// So, we append them as an embedded array instead.
//
// Collect all raw proto files from all modules to ensure imported dependencies
// are included in the FileDescriptorSet
let all_raw_files: Vec<Vec<u8>> = module_request_set
.requests()
.flat_map(|(_, req)| req.raw_files().map(|b| b.to_owned()))
.collect();

append_file_descriptor_set_bytes(
request.proto_package_name(),
&RawProtosSet {
file: all_raw_files,
},
buffer,
);
})
}
}

/// Wire-compatible FileDescriptorSet that doesn't require fully-decoded file descriptors
Expand Down
65 changes: 60 additions & 5 deletions protoc-gen-prost/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashSet},
fmt, str,
fmt,
str::{self},
};

use once_cell::sync::Lazy;
Expand Down Expand Up @@ -32,11 +33,50 @@ pub fn execute(raw_request: &[u8]) -> generator::Result {
params.prost.default_package_filename(),
params.prost.flat_output_dir,
)?;
let file_descriptor_set_generator = params
.file_descriptor_set
.then_some(FileDescriptorSetGenerator);

let files = CoreProstGenerator::new(params.prost.to_prost_config())
let file_descriptor_set_generator = if params.file_descriptor_set {
Some(if params.prost_reflect {
// When using prost-reflect, we need to include all dependencies
FileDescriptorSetGenerator::with_all_dependencies()
} else {
// Normal mode: only include files from each module
FileDescriptorSetGenerator::new()
})
} else {
None
};

let mut config = params.prost.to_prost_config();

if params.file_descriptor_set && params.prost_reflect {
let mut messages = Vec::new();
for (_, request) in module_request_set.requests() {
for file in request.files() {
let package_name = file.package();
for message in file.message_type.iter() {
messages.push(format!("{}.{}", package_name, message.name()));
}
}
}

for full_name in &messages {
config
.type_attribute(full_name, "#[derive(::prost_reflect::ReflectMessage)]")
.type_attribute(
full_name,
// This relies on the fact that file_descriptor_set_generator will create a
// variable named FILE_DESCRIPTOR_SET which contains the
// raw bytes of the file descriptor set.
r#"#[prost_reflect(file_descriptor_set_bytes = "FILE_DESCRIPTOR_SET")]"#,
)
.type_attribute(
full_name,
format!("#[prost_reflect(message_name = \"{}\")]", full_name),
);
}
}

let files = CoreProstGenerator::new(config)
.chain(file_descriptor_set_generator)
.generate(&module_request_set)?;

Expand Down Expand Up @@ -237,6 +277,10 @@ struct Parameters {

/// Whether a file descriptor set has been requested in each module
file_descriptor_set: bool,

/// Whether to generate prost-reflect trait implementations for the generated
/// rust types using prost-reflect-build
prost_reflect: bool,
}

/// Parameters used to configure the underlying Prost generator
Expand Down Expand Up @@ -549,6 +593,17 @@ impl str::FromStr for Parameters {
param: "file_descriptor_set",
value: "false",
} => (),
Param::Parameter {
param: "prost_reflect",
}
| Param::Value {
param: "prost_reflect",
value: "true",
} => ret_val.prost_reflect = true,
Param::Value {
param: "prost_reflect",
value: "false",
} => (),
_ => return Err(InvalidParameter::from(param)),
}
}
Expand Down
Loading