protobuf_codegen3/
lib.rs

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
81extern crate protobuf;
82
83use std::collections::hash_map::HashMap;
84use std::fmt::Write as FmtWrite;
85use std::fs::File;
86use std::io;
87use std::io::Write;
88use std::path::Path;
89
90use protobuf::compiler_plugin;
91use protobuf::descriptor::*;
92use protobuf::Message;
93
94mod customize;
95mod enums;
96mod extensions;
97mod field;
98mod file;
99mod file_and_mod;
100mod file_descriptor;
101#[doc(hidden)]
102pub mod float;
103mod inside;
104mod message;
105mod oneof;
106mod protobuf_name;
107mod rust_name;
108mod rust_types_values;
109mod serde;
110mod well_known_types;
111
112pub(crate) mod rust;
113pub(crate) mod scope;
114pub(crate) mod strx;
115pub(crate) mod syntax;
116
117use customize::customize_from_rustproto_for_file;
118#[doc(hidden)]
119pub use customize::Customize;
120
121pub mod code_writer;
122
123use inside::protobuf_crate_path;
124#[doc(hidden)]
125pub use protobuf_name::ProtobufAbsolutePath;
126#[doc(hidden)]
127pub use protobuf_name::ProtobufIdent;
128#[doc(hidden)]
129pub use protobuf_name::ProtobufRelativePath;
130use scope::FileScope;
131use scope::RootScope;
132
133use self::code_writer::CodeWriter;
134use self::enums::*;
135use self::extensions::*;
136use self::message::*;
137use crate::file::proto_path_to_rust_mod;
138
139fn 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
158fn 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
228struct GenFileResult {
229    compiler_plugin_result: compiler_plugin::GenResult,
230    mod_name: String,
231}
232
233fn 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
301fn 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)]
320pub 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)]
356pub 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)]
376pub 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)]
385pub fn proto_name_to_rs(name: &str) -> String {
386    format!("{}.rs", proto_path_to_rust_mod(name))
387}