rmqtt_protobuf_build/
lib.rs1#[cfg(feature = "prost-codec")]
9mod wrapper;
10
11#[cfg(feature = "protobuf-codec")]
12mod protobuf_impl;
13
14#[cfg(feature = "prost-codec")]
15mod prost_impl;
16
17use bitflags::bitflags;
18use regex::Regex;
19use std::env;
20use std::env::var;
21use std::fmt::Write as _;
22use std::fs::{self, File};
23use std::io::Write;
24use std::path::{Path, PathBuf};
25use std::process::Command;
26use std::str::from_utf8;
27
28impl Builder {
29 pub fn generate_files(&self) {
34 #[cfg(feature = "prost-codec")]
35 self.generate_files_prost();
36 #[cfg(all(feature = "protobuf-codec", not(feature = "prost-codec")))]
37 self.generate_files_protobuf();
38 }
39}
40
41fn get_protoc() -> String {
44 if let Ok(s) = var("PROTOC") {
46 check_protoc_version(&s).expect("PROTOC version not usable");
47 return s;
48 }
49
50 if let Ok(s) = check_protoc_version("protoc") {
51 return s;
52 }
53
54 #[cfg(windows)]
56 {
57 let bin_path = Path::new(env!("CARGO_MANIFEST_DIR"))
58 .join("bin")
59 .join("protoc-win32.exe");
60 bin_path.display().to_string()
61 }
62
63 #[cfg(not(windows))]
64 protobuf_src::protoc().display().to_string()
65}
66
67fn check_protoc_version(protoc: &str) -> Result<String, ()> {
68 let ver_re = Regex::new(r"([0-9]+)\.([0-9]+)(\.[0-9])?").unwrap();
69 let output = Command::new(protoc).arg("--version").output();
70 match output {
71 Ok(o) => {
72 let caps = ver_re.captures(from_utf8(&o.stdout).unwrap()).unwrap();
73 let major = caps.get(1).unwrap().as_str().parse::<i16>().unwrap();
74 let minor = caps.get(2).unwrap().as_str().parse::<i16>().unwrap();
75 if (major, minor) >= (3, 1) {
76 return Ok(protoc.to_owned());
77 }
78 println!("The system `protoc` version mismatch, require >= 3.1.0, got {}.{}.x, fallback to the bundled `protoc`", major, minor);
79 }
80 Err(_) => println!("`protoc` not in PATH, try using the bundled protoc"),
81 };
82
83 Err(())
84}
85
86pub struct Builder {
87 files: Vec<String>,
88 includes: Vec<String>,
89 black_list: Vec<String>,
90 out_dir: String,
91 #[cfg(feature = "prost-codec")]
92 wrapper_opts: GenOpt,
93 package_name: Option<String>,
94 #[cfg(feature = "grpcio-protobuf-codec")]
95 re_export_services: bool,
96}
97
98impl Builder {
99 pub fn new() -> Builder {
100 Builder {
101 files: Vec::new(),
102 includes: vec!["include".to_owned(), "proto".to_owned()],
103 black_list: vec![
104 "protobuf".to_owned(),
105 "google".to_owned(),
106 "gogoproto".to_owned(),
107 ],
108 out_dir: format!("{}/protos", var("OUT_DIR").expect("No OUT_DIR defined")),
109 #[cfg(feature = "prost-codec")]
110 wrapper_opts: GenOpt::all(),
111 package_name: None,
112 #[cfg(feature = "grpcio-protobuf-codec")]
113 re_export_services: true,
114 }
115 }
116
117 pub fn include_google_protos(&mut self) -> &mut Self {
118 let path = format!("{}/include", env!("CARGO_MANIFEST_DIR"));
119 self.includes.push(path);
120 self
121 }
122
123 pub fn generate(&self) {
124 assert!(!self.files.is_empty(), "No files specified for generation");
125 self.prep_out_dir();
126 self.generate_files();
127 self.generate_mod_file();
128 }
129
130 #[cfg(feature = "prost-codec")]
133 pub fn wrapper_options(&mut self, wrapper_opts: GenOpt) -> &mut Self {
134 self.wrapper_opts = wrapper_opts;
135 self
136 }
137
138 pub fn search_dir_for_protos(&mut self, proto_dir: &str) -> &mut Self {
140 self.files = fs::read_dir(proto_dir)
141 .expect("Couldn't read proto directory")
142 .filter_map(|e| {
143 let e = e.expect("Couldn't list file");
144 if e.file_type().expect("File broken").is_dir() {
145 None
146 } else {
147 Some(format!("{}/{}", proto_dir, e.file_name().to_string_lossy()))
148 }
149 })
150 .collect();
151 self
152 }
153
154 pub fn files<T: ToString>(&mut self, files: &[T]) -> &mut Self {
155 self.files = files.iter().map(|t| t.to_string()).collect();
156 self
157 }
158
159 pub fn includes<T: ToString>(&mut self, includes: &[T]) -> &mut Self {
160 self.includes = includes.iter().map(|t| t.to_string()).collect();
161 self
162 }
163
164 pub fn append_include(&mut self, include: impl Into<String>) -> &mut Self {
165 self.includes.push(include.into());
166 self
167 }
168
169 pub fn black_list<T: ToString>(&mut self, black_list: &[T]) -> &mut Self {
170 self.black_list = black_list.iter().map(|t| t.to_string()).collect();
171 self
172 }
173
174 pub fn append_to_black_list(&mut self, include: impl Into<String>) -> &mut Self {
179 self.black_list.push(include.into());
180 self
181 }
182
183 pub fn out_dir(&mut self, out_dir: impl Into<String>) -> &mut Self {
184 self.out_dir = out_dir.into();
185 self
186 }
187
188 pub fn package_name(&mut self, package_name: impl Into<String>) -> &mut Self {
194 self.package_name = Some(package_name.into());
195 self
196 }
197
198 #[cfg(feature = "grpcio-protobuf-codec")]
201 pub fn re_export_services(&mut self, re_export_services: bool) -> &mut Self {
202 self.re_export_services = re_export_services;
203 self
204 }
205
206 fn generate_mod_file(&self) {
207 let mut f = File::create(format!("{}/mod.rs", self.out_dir)).unwrap();
208
209 let modules = self.list_rs_files().filter_map(|path| {
210 let name = path.file_stem().unwrap().to_str().unwrap();
211 if name.starts_with("wrapper_")
212 || name == "mod"
213 || self.black_list.iter().any(|i| name.contains(i))
214 {
215 return None;
216 }
217 Some((name.replace('-', "_"), name.to_owned()))
218 });
219
220 let mut exports = String::new();
221 for (module, file_name) in modules {
222 let wrapper_path = format!("{}/wrapper_{}.rs", self.out_dir, file_name);
223 let wrapper_exists = Path::new(&wrapper_path).exists();
224
225 if cfg!(not(feature = "prost-codec")) {
226 if wrapper_exists {
227 writeln!(f, "pub mod {} {{", module).unwrap();
230 writeln!(f, " include!(\"{}.rs\");", file_name).unwrap();
231 writeln!(f, " include!(\"wrapper_{}.rs\");", file_name).unwrap();
232 writeln!(f, "}}").unwrap();
233 if self.package_name.is_some() {
234 writeln!(exports, "pub use super::{}::*;", module).unwrap();
235 }
236 } else if self.package_name.is_some() {
237 writeln!(exports, "pub use super::{}::*;", module).unwrap();
238 writeln!(f, "mod {};", module).unwrap();
239 } else {
240 writeln!(f, "pub ").unwrap();
241 writeln!(f, "mod {};", module).unwrap();
242 }
243 continue;
244 }
245
246 let mut level = 0;
247 for part in module.split('.') {
248 writeln!(f, "pub mod {} {{", part).unwrap();
249 level += 1;
250 }
251 writeln!(f, "include!(\"{}.rs\");", file_name,).unwrap();
252 if wrapper_exists {
253 writeln!(f, "include!(\"wrapper_{}.rs\");", file_name,).unwrap();
254 }
255 writeln!(f, "{}", "}\n".repeat(level)).unwrap();
256 }
257
258 if !exports.is_empty() {
259 writeln!(
260 f,
261 "pub mod {} {{ {} }}",
262 self.package_name.as_ref().unwrap(),
263 exports
264 )
265 .unwrap();
266 }
267 }
268
269 fn prep_out_dir(&self) {
270 if Path::new(&self.out_dir).exists() {
271 fs::remove_dir_all(&self.out_dir).unwrap();
272 }
273 fs::create_dir_all(&self.out_dir).unwrap();
274 }
275
276 fn list_rs_files(&self) -> impl Iterator<Item = PathBuf> {
278 fs::read_dir(&self.out_dir)
279 .expect("Couldn't read directory")
280 .filter_map(|e| {
281 let path = e.expect("Couldn't list file").path();
282 if path.extension() == Some(std::ffi::OsStr::new("rs")) {
283 Some(path)
284 } else {
285 None
286 }
287 })
288 }
289}
290
291impl Default for Builder {
292 fn default() -> Builder {
293 Builder::new()
294 }
295}
296
297bitflags! {
298 pub struct GenOpt: u32 {
299 const MESSAGE = 0b0000_0001;
301 const TRIVIAL_GET = 0b0000_0010;
303 const TRIVIAL_SET = 0b0000_0100;
305 const NEW = 0b0000_1000;
307 const CLEAR = 0b0001_0000;
309 const HAS = 0b0010_0000;
311 const MUT = 0b0100_0000;
313 const TAKE = 0b1000_0000;
315 const NO_MSG = Self::TRIVIAL_GET.bits
317 | Self::TRIVIAL_SET.bits
318 | Self::CLEAR.bits
319 | Self::HAS.bits
320 | Self::MUT.bits
321 | Self::TAKE.bits;
322 const ACCESSOR = Self::TRIVIAL_GET.bits
324 | Self::TRIVIAL_SET.bits
325 | Self::MUT.bits
326 | Self::TAKE.bits;
327 }
328}