1 //! # Protobuf code generator
2 //!
3 //! This crate contains protobuf code generator implementation
4 //! and a `protoc-gen-rust` `protoc` plugin.
5 //!
6 //! This crate:
7 //! * provides `protoc-gen-rust` plugin for `protoc` command
8 //! * implement protobuf codegen
9 //!
10 //! This crate is not meant to be used directly, in fact, it does not provide any public API
11 //! (except for `protoc-gen-rust` binary).
12 //!
13 //! Code can be generated with either:
14 //! * `protoc-gen-rust` plugin for `protoc` or
15 //! * [`protoc-rust`](https://docs.rs/protoc) crate
16 //!   (code generator which depends on `protoc` binary for parsing of `.proto` files)
17 //! * [`protobuf-codegen-pure`](https://docs.rs/protobuf-codegen-pure) crate,
18 //!   similar API to `protoc-rust`, but uses pure rust parser of `.proto` files.
19 //!
20 //! # `protoc-gen-rust` plugin for `protoc`
21 //!
22 //! When non-cargo build system is used, consider using standard protobuf code generation pattern:
23 //! `protoc` command does all the work of handling paths and parsing `.proto` files.
24 //! When `protoc` is invoked with `--rust_out=` option, it invokes `protoc-gen-rust` plugin.
25 //! provided by this crate.
26 //!
27 //! When building with cargo, consider using `protoc-rust` or `protobuf-codegen-pure` crates.
28 //!
29 //! ## How to use `protoc-gen-rust` if you have to
30 //!
31 //! (Note `protoc` can be invoked programmatically with
32 //! [protoc crate](https://docs.rs/protoc))
33 //!
34 //! 0) Install protobuf for `protoc` binary.
35 //!
36 //! On OS X [Homebrew](https://github.com/Homebrew/brew) can be used:
37 //!
38 //! ```sh
39 //! brew install protobuf
40 //! ```
41 //!
42 //! On Ubuntu, `protobuf-compiler` package can be installed:
43 //!
44 //! ```sh
45 //! apt-get install protobuf-compiler
46 //! ```
47 //!
48 //! Protobuf is needed only for code generation, `rust-protobuf` runtime
49 //! does not use `protobuf` library.
50 //!
51 //! 1) Install `protoc-gen-rust` program (which is `protoc` plugin)
52 //!
53 //! It can be installed either from source or with `cargo install protobuf` command.
54 //!
55 //! 2) Add `protoc-gen-rust` to $PATH
56 //!
57 //! If you installed it with cargo, it should be
58 //!
59 //! ```sh
60 //! PATH="$HOME/.cargo/bin:$PATH"
61 //! ```
62 //!
63 //! 3) Generate .rs files:
64 //!
65 //! ```sh
66 //! protoc --rust_out . foo.proto
67 //! ```
68 //!
69 //! This will generate .rs files in current directory.
70 //!
71 //! # Version 2
72 //!
73 //! This is documentation for version 2 of the crate.
74 //!
75 //! [Version 3 of the crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha)
76 //! (currently in development) encapsulates both `protoc` and pure codegens in this crate.
77 
78 #![deny(rustdoc::broken_intra_doc_links)]
79 #![deny(missing_docs)]
80 
81 extern crate protobuf;
82 
83 use std::collections::hash_map::HashMap;
84 use std::fmt::Write as FmtWrite;
85 use std::fs::File;
86 use std::io;
87 use std::io::Write;
88 use std::path::Path;
89 
90 use protobuf::compiler_plugin;
91 use protobuf::descriptor::*;
92 use protobuf::Message;
93 
94 mod customize;
95 mod enums;
96 mod extensions;
97 mod field;
98 mod file;
99 mod file_and_mod;
100 mod file_descriptor;
101 #[doc(hidden)]
102 pub mod float;
103 mod inside;
104 mod message;
105 mod oneof;
106 mod protobuf_name;
107 mod rust_name;
108 mod rust_types_values;
109 mod serde;
110 mod well_known_types;
111 
112 pub(crate) mod rust;
113 pub(crate) mod scope;
114 pub(crate) mod strx;
115 pub(crate) mod syntax;
116 
117 use customize::customize_from_rustproto_for_file;
118 #[doc(hidden)]
119 pub use customize::Customize;
120 
121 pub mod code_writer;
122 
123 use inside::protobuf_crate_path;
124 #[doc(hidden)]
125 pub use protobuf_name::ProtobufAbsolutePath;
126 #[doc(hidden)]
127 pub use protobuf_name::ProtobufIdent;
128 #[doc(hidden)]
129 pub use protobuf_name::ProtobufRelativePath;
130 use scope::FileScope;
131 use scope::RootScope;
132 
133 use self::code_writer::CodeWriter;
134 use self::enums::*;
135 use self::extensions::*;
136 use self::message::*;
137 use crate::file::proto_path_to_rust_mod;
138 
escape_byte(s: &mut String, b: u8)139 fn escape_byte(s: &mut String, b: u8) {
140     if b == b'\n' {
141         write!(s, "\\n").unwrap();
142     } else if b == b'\r' {
143         write!(s, "\\r").unwrap();
144     } else if b == b'\t' {
145         write!(s, "\\t").unwrap();
146     } else if b == b'\\' || b == b'"' {
147         write!(s, "\\{}", b as char).unwrap();
148     } else if b == b'\0' {
149         write!(s, "\\0").unwrap();
150     // ASCII printable except space
151     } else if b > 0x20 && b < 0x7f {
152         write!(s, "{}", b as char).unwrap();
153     } else {
154         write!(s, "\\x{:02x}", b).unwrap();
155     }
156 }
157 
write_file_descriptor_data( file: &FileDescriptorProto, customize: &Customize, w: &mut CodeWriter, )158 fn write_file_descriptor_data(
159     file: &FileDescriptorProto,
160     customize: &Customize,
161     w: &mut CodeWriter,
162 ) {
163     let fdp_bytes = file.write_to_bytes().unwrap();
164     w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\");
165     w.indented(|w| {
166         const MAX_LINE_LEN: usize = 72;
167 
168         let mut s = String::new();
169         for &b in &fdp_bytes {
170             let prev_len = s.len();
171             escape_byte(&mut s, b);
172             let truncate = s.len() > MAX_LINE_LEN;
173             if truncate {
174                 s.truncate(prev_len);
175             }
176             if truncate || s.len() == MAX_LINE_LEN {
177                 write!(s, "\\").unwrap();
178                 w.write_line(&s);
179                 s.clear();
180             }
181             if truncate {
182                 escape_byte(&mut s, b);
183             }
184         }
185         if !s.is_empty() {
186             write!(s, "\\").unwrap();
187             w.write_line(&s);
188             s.clear();
189         }
190     });
191     w.write_line("\";");
192     w.write_line("");
193     w.lazy_static(
194         "file_descriptor_proto_lazy",
195         &format!(
196             "{}::descriptor::FileDescriptorProto",
197             protobuf_crate_path(customize)
198         ),
199         customize,
200     );
201     w.write_line("");
202     w.def_fn(
203         &format!(
204             "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto",
205             protobuf_crate_path(customize)
206         ),
207         |w| {
208             w.write_line(&format!(
209                 "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()",
210                 protobuf_crate_path(customize)
211             ));
212         },
213     );
214     w.write_line("");
215     w.pub_fn(
216         &format!(
217             "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto",
218             protobuf_crate_path(customize)
219         ),
220         |w| {
221             w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| {
222                 w.write_line("parse_descriptor_proto()");
223             });
224         },
225     );
226 }
227 
228 struct GenFileResult {
229     compiler_plugin_result: compiler_plugin::GenResult,
230     mod_name: String,
231 }
232 
gen_file( file: &FileDescriptorProto, _files_map: &HashMap<&str, &FileDescriptorProto>, root_scope: &RootScope, customize: &Customize, ) -> GenFileResult233 fn gen_file(
234     file: &FileDescriptorProto,
235     _files_map: &HashMap<&str, &FileDescriptorProto>,
236     root_scope: &RootScope,
237     customize: &Customize,
238 ) -> GenFileResult {
239     // TODO: use it
240     let mut customize = customize.clone();
241     // options specified in invocation have precedence over options specified in file
242     customize.update_with(&customize_from_rustproto_for_file(file.get_options()));
243 
244     let scope = FileScope {
245         file_descriptor: file,
246     }
247     .to_scope();
248     let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
249         file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME
250     });
251 
252     let mut v = Vec::new();
253 
254     {
255         let mut w = CodeWriter::new(&mut v);
256 
257         w.write_generated_by("rust-protobuf", env!("CARGO_PKG_VERSION"));
258         w.write_line(&format!("//! Generated file from `{}`", file.get_name()));
259         if customize.inside_protobuf != Some(true) {
260             w.write_line("");
261             w.write_line("/// Generated files are compatible only with the same version");
262             w.write_line("/// of protobuf runtime.");
263             w.commented(|w| {
264                 w.write_line(&format!(
265                     "const _PROTOBUF_VERSION_CHECK: () = {}::{};",
266                     protobuf_crate_path(&customize),
267                     protobuf::VERSION_IDENT
268                 ));
269             })
270         }
271 
272         for message in &scope.get_messages() {
273             // ignore map entries, because they are not used in map fields
274             if message.map_entry().is_none() {
275                 w.write_line("");
276                 MessageGen::new(message, &root_scope, &customize).write(&mut w);
277             }
278         }
279         for enum_type in &scope.get_enums() {
280             w.write_line("");
281             EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w);
282         }
283 
284         write_extensions(file, &root_scope, &mut w, &customize);
285 
286         if !lite_runtime {
287             w.write_line("");
288             write_file_descriptor_data(file, &customize, &mut w);
289         }
290     }
291 
292     GenFileResult {
293         compiler_plugin_result: compiler_plugin::GenResult {
294             name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())),
295             content: v,
296         },
297         mod_name: proto_path_to_rust_mod(file.get_name()).into_string(),
298     }
299 }
300 
gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult301 fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult {
302     let mut v = Vec::new();
303     let mut w = CodeWriter::new(&mut v);
304     w.comment("@generated");
305     w.write_line("");
306     for m in mods {
307         w.write_line(&format!("pub mod {};", m));
308     }
309     drop(w);
310     compiler_plugin::GenResult {
311         name: "mod.rs".to_owned(),
312         content: v,
313     }
314 }
315 
316 // This function is also used externally by cargo plugin
317 // https://github.com/plietar/rust-protobuf-build
318 // So be careful changing its signature.
319 #[doc(hidden)]
gen( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], customize: &Customize, ) -> Vec<compiler_plugin::GenResult>320 pub fn gen(
321     file_descriptors: &[FileDescriptorProto],
322     files_to_generate: &[String],
323     customize: &Customize,
324 ) -> Vec<compiler_plugin::GenResult> {
325     let root_scope = RootScope {
326         file_descriptors: file_descriptors,
327     };
328 
329     let mut results: Vec<compiler_plugin::GenResult> = Vec::new();
330     let files_map: HashMap<&str, &FileDescriptorProto> =
331         file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
332 
333     let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect();
334 
335     let mut mods = Vec::new();
336 
337     for file_name in files_to_generate {
338         let file = files_map.get(&file_name[..]).expect(&format!(
339             "file not found in file descriptors: {:?}, files: {:?}",
340             file_name, all_file_names
341         ));
342 
343         let gen_file_result = gen_file(file, &files_map, &root_scope, customize);
344         results.push(gen_file_result.compiler_plugin_result);
345         mods.push(gen_file_result.mod_name);
346     }
347 
348     if customize.gen_mod_rs.unwrap_or(false) {
349         results.push(gen_mod_rs(&mods));
350     }
351 
352     results
353 }
354 
355 #[doc(hidden)]
gen_and_write( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], out_dir: &Path, customize: &Customize, ) -> io::Result<()>356 pub fn gen_and_write(
357     file_descriptors: &[FileDescriptorProto],
358     files_to_generate: &[String],
359     out_dir: &Path,
360     customize: &Customize,
361 ) -> io::Result<()> {
362     let results = gen(file_descriptors, files_to_generate, customize);
363 
364     for r in &results {
365         let mut file_path = out_dir.to_owned();
366         file_path.push(&r.name);
367         let mut file_writer = File::create(&file_path)?;
368         file_writer.write_all(&r.content)?;
369         file_writer.flush()?;
370     }
371 
372     Ok(())
373 }
374 
375 #[doc(hidden)]
protoc_gen_rust_main()376 pub fn protoc_gen_rust_main() {
377     compiler_plugin::plugin_main_2(|r| {
378         let customize = Customize::parse_from_parameter(r.parameter).expect("parse options");
379         gen(r.file_descriptors, r.files_to_generate, &customize)
380     });
381 }
382 
383 /// Used in protobuf-codegen-identical-test
384 #[doc(hidden)]
proto_name_to_rs(name: &str) -> String385 pub fn proto_name_to_rs(name: &str) -> String {
386     format!("{}.rs", proto_path_to_rust_mod(name))
387 }
388