1 //! This crate implement protobuf codegen.
2 //!
3 //! This crate:
4 //! * provides `protoc-gen-rust` plugin for `protoc` command
5 //! * implement protobuf codegen
6 //!
7 //! This crate is not meant to be used directly, in fact, it does not provide any public API
8 //! (except for `protoc-gen-rust` binary).
9 //!
10 //! Code can be generated with either:
11 //! * `protoc-gen-rust` binary or
12 //! * `protoc-rust` crate (codegen which depends on `protoc` binary for parsing)
13 //! * `protobuf-codegen-pure` crate
14
15 #![deny(broken_intra_doc_links)]
16 #![deny(missing_docs)]
17
18 extern crate protobuf;
19
20 use std::collections::hash_map::HashMap;
21 use std::fmt::Write as FmtWrite;
22 use std::fs::File;
23 use std::io;
24 use std::io::Write;
25 use std::path::Path;
26
27 use protobuf::compiler_plugin;
28 use protobuf::descriptor::*;
29 use protobuf::Message;
30
31 mod customize;
32 mod enums;
33 mod extensions;
34 mod field;
35 mod file;
36 mod file_and_mod;
37 mod file_descriptor;
38 #[doc(hidden)]
39 pub mod float;
40 mod inside;
41 mod message;
42 mod oneof;
43 mod protobuf_name;
44 mod rust_name;
45 mod rust_types_values;
46 mod serde;
47 mod well_known_types;
48
49 pub(crate) mod rust;
50 pub(crate) mod scope;
51 pub(crate) mod strx;
52 pub(crate) mod syntax;
53
54 use customize::customize_from_rustproto_for_file;
55 #[doc(hidden)]
56 pub use customize::Customize;
57
58 pub mod code_writer;
59
60 use self::code_writer::CodeWriter;
61 use self::enums::*;
62 use self::extensions::*;
63 use self::message::*;
64 use inside::protobuf_crate_path;
65 use scope::FileScope;
66 use scope::RootScope;
67
68 use crate::file::proto_path_to_rust_mod;
69
70 #[doc(hidden)]
71 pub use protobuf_name::ProtobufAbsolutePath;
72 #[doc(hidden)]
73 pub use protobuf_name::ProtobufIdent;
74 #[doc(hidden)]
75 pub use protobuf_name::ProtobufRelativePath;
76
escape_byte(s: &mut String, b: u8)77 fn escape_byte(s: &mut String, b: u8) {
78 if b == b'\n' {
79 write!(s, "\\n").unwrap();
80 } else if b == b'\r' {
81 write!(s, "\\r").unwrap();
82 } else if b == b'\t' {
83 write!(s, "\\t").unwrap();
84 } else if b == b'\\' || b == b'"' {
85 write!(s, "\\{}", b as char).unwrap();
86 } else if b == b'\0' {
87 write!(s, "\\0").unwrap();
88 // ASCII printable except space
89 } else if b > 0x20 && b < 0x7f {
90 write!(s, "{}", b as char).unwrap();
91 } else {
92 write!(s, "\\x{:02x}", b).unwrap();
93 }
94 }
95
write_file_descriptor_data( file: &FileDescriptorProto, customize: &Customize, w: &mut CodeWriter, )96 fn write_file_descriptor_data(
97 file: &FileDescriptorProto,
98 customize: &Customize,
99 w: &mut CodeWriter,
100 ) {
101 let fdp_bytes = file.write_to_bytes().unwrap();
102 w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\");
103 w.indented(|w| {
104 const MAX_LINE_LEN: usize = 72;
105
106 let mut s = String::new();
107 for &b in &fdp_bytes {
108 let prev_len = s.len();
109 escape_byte(&mut s, b);
110 let truncate = s.len() > MAX_LINE_LEN;
111 if truncate {
112 s.truncate(prev_len);
113 }
114 if truncate || s.len() == MAX_LINE_LEN {
115 write!(s, "\\").unwrap();
116 w.write_line(&s);
117 s.clear();
118 }
119 if truncate {
120 escape_byte(&mut s, b);
121 }
122 }
123 if !s.is_empty() {
124 write!(s, "\\").unwrap();
125 w.write_line(&s);
126 s.clear();
127 }
128 });
129 w.write_line("\";");
130 w.write_line("");
131 w.lazy_static(
132 "file_descriptor_proto_lazy",
133 &format!(
134 "{}::descriptor::FileDescriptorProto",
135 protobuf_crate_path(customize)
136 ),
137 customize,
138 );
139 w.write_line("");
140 w.def_fn(
141 &format!(
142 "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto",
143 protobuf_crate_path(customize)
144 ),
145 |w| {
146 w.write_line(&format!(
147 "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()",
148 protobuf_crate_path(customize)
149 ));
150 },
151 );
152 w.write_line("");
153 w.pub_fn(
154 &format!(
155 "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto",
156 protobuf_crate_path(customize)
157 ),
158 |w| {
159 w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| {
160 w.write_line("parse_descriptor_proto()");
161 });
162 },
163 );
164 }
165
166 struct GenFileResult {
167 compiler_plugin_result: compiler_plugin::GenResult,
168 mod_name: String,
169 }
170
gen_file( file: &FileDescriptorProto, _files_map: &HashMap<&str, &FileDescriptorProto>, root_scope: &RootScope, customize: &Customize, ) -> GenFileResult171 fn gen_file(
172 file: &FileDescriptorProto,
173 _files_map: &HashMap<&str, &FileDescriptorProto>,
174 root_scope: &RootScope,
175 customize: &Customize,
176 ) -> GenFileResult {
177 // TODO: use it
178 let mut customize = customize.clone();
179 // options specified in invocation have precedence over options specified in file
180 customize.update_with(&customize_from_rustproto_for_file(file.get_options()));
181
182 let scope = FileScope {
183 file_descriptor: file,
184 }
185 .to_scope();
186 let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
187 file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME
188 });
189
190 let mut v = Vec::new();
191
192 {
193 let mut w = CodeWriter::new(&mut v);
194
195 w.write_generated_by("rust-protobuf", "2.22.1");
196 w.write_line(&format!("//! Generated file from `{}`", file.get_name()));
197 if customize.inside_protobuf != Some(true) {
198 w.write_line("");
199 w.write_line("/// Generated files are compatible only with the same version");
200 w.write_line("/// of protobuf runtime.");
201 w.commented(|w| {
202 w.write_line(&format!(
203 "const _PROTOBUF_VERSION_CHECK: () = {}::{};",
204 protobuf_crate_path(&customize),
205 protobuf::VERSION_IDENT
206 ));
207 })
208 }
209
210 for message in &scope.get_messages() {
211 // ignore map entries, because they are not used in map fields
212 if message.map_entry().is_none() {
213 w.write_line("");
214 MessageGen::new(message, &root_scope, &customize).write(&mut w);
215 }
216 }
217 for enum_type in &scope.get_enums() {
218 w.write_line("");
219 EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w);
220 }
221
222 write_extensions(file, &root_scope, &mut w, &customize);
223
224 if !lite_runtime {
225 w.write_line("");
226 write_file_descriptor_data(file, &customize, &mut w);
227 }
228 }
229
230 GenFileResult {
231 compiler_plugin_result: compiler_plugin::GenResult {
232 name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())),
233 content: v,
234 },
235 mod_name: proto_path_to_rust_mod(file.get_name()).into_string(),
236 }
237 }
238
gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult239 fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult {
240 let mut v = Vec::new();
241 let mut w = CodeWriter::new(&mut v);
242 w.comment("@generated");
243 w.write_line("");
244 for m in mods {
245 w.write_line(&format!("pub mod {};", m));
246 }
247 drop(w);
248 compiler_plugin::GenResult {
249 name: "mod.rs".to_owned(),
250 content: v,
251 }
252 }
253
254 // This function is also used externally by cargo plugin
255 // https://github.com/plietar/rust-protobuf-build
256 // So be careful changing its signature.
257 #[doc(hidden)]
gen( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], customize: &Customize, ) -> Vec<compiler_plugin::GenResult>258 pub fn gen(
259 file_descriptors: &[FileDescriptorProto],
260 files_to_generate: &[String],
261 customize: &Customize,
262 ) -> Vec<compiler_plugin::GenResult> {
263 let root_scope = RootScope {
264 file_descriptors: file_descriptors,
265 };
266
267 let mut results: Vec<compiler_plugin::GenResult> = Vec::new();
268 let files_map: HashMap<&str, &FileDescriptorProto> =
269 file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
270
271 let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect();
272
273 let mut mods = Vec::new();
274
275 for file_name in files_to_generate {
276 let file = files_map.get(&file_name[..]).expect(&format!(
277 "file not found in file descriptors: {:?}, files: {:?}",
278 file_name, all_file_names
279 ));
280
281 let gen_file_result = gen_file(file, &files_map, &root_scope, customize);
282 results.push(gen_file_result.compiler_plugin_result);
283 mods.push(gen_file_result.mod_name);
284 }
285
286 if customize.gen_mod_rs.unwrap_or(false) {
287 results.push(gen_mod_rs(&mods));
288 }
289
290 results
291 }
292
293 #[doc(hidden)]
gen_and_write( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], out_dir: &Path, customize: &Customize, ) -> io::Result<()>294 pub fn gen_and_write(
295 file_descriptors: &[FileDescriptorProto],
296 files_to_generate: &[String],
297 out_dir: &Path,
298 customize: &Customize,
299 ) -> io::Result<()> {
300 let results = gen(file_descriptors, files_to_generate, customize);
301
302 for r in &results {
303 let mut file_path = out_dir.to_owned();
304 file_path.push(&r.name);
305 let mut file_writer = File::create(&file_path)?;
306 file_writer.write_all(&r.content)?;
307 file_writer.flush()?;
308 }
309
310 Ok(())
311 }
312
313 #[doc(hidden)]
protoc_gen_rust_main()314 pub fn protoc_gen_rust_main() {
315 compiler_plugin::plugin_main_2(|r| {
316 let customize = Customize::parse_from_parameter(r.parameter).expect("parse options");
317 gen(r.file_descriptors, r.files_to_generate, &customize)
318 });
319 }
320
321 /// Used in protobuf-codegen-identical-test
322 #[doc(hidden)]
proto_name_to_rs(name: &str) -> String323 pub fn proto_name_to_rs(name: &str) -> String {
324 format!("{}.rs", proto_path_to_rust_mod(name))
325 }
326