protoc_gen_prost_crate/generator/
features.rs1use std::{
2 collections::{BTreeMap, BTreeSet},
3 rc::Rc,
4};
5
6use prost_types::compiler::code_generator_response::File;
7use protoc_gen_prost::{Generator, ModuleRequest, ModuleRequestSet, Result};
8
9use crate::PackageLimiter;
10
11pub(crate) struct FeaturesGenerator<'a> {
12 include_filename: &'a str,
13 package_separator: &'a str,
14 limiter: Rc<PackageLimiter>,
15}
16
17impl<'a> FeaturesGenerator<'a> {
18 pub(crate) fn new(
19 include_filename: &'a str,
20 package_separator: &'a str,
21 limiter: Rc<PackageLimiter>,
22 ) -> Self {
23 Self {
24 include_filename,
25 package_separator,
26 limiter,
27 }
28 }
29}
30
31impl<'a> Generator for FeaturesGenerator<'a> {
32 fn generate(&mut self, module_request_set: &ModuleRequestSet) -> Result {
33 let mut files = Vec::new();
34
35 let dep_tree = module_request_set
36 .requests()
37 .map(|(_, v)| v)
38 .collect::<PackageDependencies>();
39
40 let mut buf = String::new();
41
42 if !dep_tree.dependency_graph.is_empty() {
43 buf.push_str(
44 "# @@protoc_deletion_point(features)\n# This section is automatically generated \
45 by protoc-gen-prost-crate.\n# Changes in this area may be lost on regeneration.\n",
46 );
47 buf.push_str("proto_full = [");
48 for feature in dep_tree.dependency_graph.keys() {
49 if !self.limiter.is_allowed(feature) {
50 continue;
51 }
52
53 let feature_name = feature.replace('.', self.package_separator);
54 buf.push('"');
55 buf.push_str(&feature_name);
56 buf.push_str("\",");
57
58 files.push(File {
59 name: Some(self.include_filename.to_owned()),
60 content: Some(format!("#[cfg(feature = \"{feature_name}\")]\n")),
61 insertion_point: Some(format!("attribute:{feature}")),
62 ..File::default()
63 });
64 }
65 if let Some('[') = buf.pop() {
66 buf.push('[');
67 }
68 buf.push_str("]\n");
69
70 for (feature, dependencies) in dep_tree.dependency_graph {
71 if !self.limiter.is_allowed(feature) {
72 continue;
73 }
74
75 buf.push('"');
76 buf.push_str(&feature.replace('.', self.package_separator));
77 buf.push('"');
78 if dependencies.is_empty() {
79 buf.push_str(" = []\n");
80 } else {
81 buf.push_str(" = [");
82 for feature in dependencies {
83 if !self.limiter.is_allowed(feature) {
84 continue;
85 }
86
87 buf.push('"');
88 buf.push_str(&feature.replace('.', self.package_separator));
89 buf.push_str("\",");
90 }
91 if let Some('[') = buf.pop() {
92 buf.push('[');
93 }
94 buf.push_str("]\n");
95 }
96 }
97 }
98
99 files.push(File {
100 name: Some("Cargo.toml".to_string()),
101 content: Some(buf),
102 insertion_point: Some("features".to_string()),
103 ..File::default()
104 });
105
106 Ok(files)
107 }
108}
109
110struct PackageDependencies<'a> {
111 pub dependency_graph: BTreeMap<&'a str, BTreeSet<&'a str>>,
112}
113
114impl<'a> FromIterator<&'a ModuleRequest> for PackageDependencies<'a> {
115 fn from_iter<T: IntoIterator<Item = &'a ModuleRequest>>(iter: T) -> Self {
116 let requests = iter.into_iter().collect::<Vec<&ModuleRequest>>();
117 let mut features = requests
118 .iter()
119 .filter(|r| r.output_filename().is_some() && !r.proto_package_name().is_empty())
120 .map(|r| (r.proto_package_name(), BTreeSet::new()))
121 .collect::<BTreeMap<&str, BTreeSet<&str>>>();
122
123 let mut depend_on_type = |current_package: &'a str, depends_on_type: &str| {
124 if depends_on_type.starts_with('.') {
126 if let Some(&key) = features
130 .keys()
131 .rev()
132 .filter(|&&key| key != current_package)
133 .find(|&&key| depends_on_type[1..].starts_with(&format!("{key}.")))
134 {
135 features.entry(current_package).or_default().insert(key);
136 }
137 }
138 };
139
140 for file in requests.into_iter().flat_map(|r| r.files()) {
141 for message in &file.message_type {
142 for field in &message.field {
143 depend_on_type(file.package(), field.type_name());
144 }
145 }
146
147 for service in &file.service {
148 for method in &service.method {
149 depend_on_type(file.package(), method.input_type());
150 depend_on_type(file.package(), method.output_type());
151 }
152 }
153 }
154
155 Self {
156 dependency_graph: features,
157 }
158 }
159}