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