blob: e65c1bd5f21acf771b6f9543745d99715b979123 [file] [log] [blame]
Chih-Hung Hsieh92ff6052020-06-10 20:18:39 -07001//! 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(intra_doc_link_resolution_failure)]
16#![deny(missing_docs)]
17
18extern crate protobuf;
19
20use std::collections::hash_map::HashMap;
21use std::fmt::Write as FmtWrite;
22use std::fs::File;
23use std::io;
24use std::io::Write;
25use std::path::Path;
26
27use protobuf::compiler_plugin;
28use protobuf::descriptor::*;
29use protobuf::Message;
30
31mod customize;
32mod enums;
33mod extensions;
34mod field;
35mod file;
36mod file_and_mod;
37mod file_descriptor;
38#[doc(hidden)]
39pub mod float;
40mod inside;
41mod message;
42mod oneof;
43mod protobuf_name;
44mod rust_name;
45mod rust_types_values;
46mod serde;
47mod well_known_types;
48
49pub(crate) mod rust;
50pub(crate) mod scope;
51pub(crate) mod strx;
52pub(crate) mod syntax;
53
54use customize::customize_from_rustproto_for_file;
55#[doc(hidden)]
56pub use customize::Customize;
57
58pub mod code_writer;
59
60use self::code_writer::CodeWriter;
61use self::enums::*;
62use self::extensions::*;
63use self::message::*;
64use file::proto_path_to_rust_mod;
65use inside::protobuf_crate_path;
66use scope::FileScope;
67use scope::RootScope;
68
69fn escape_byte(s: &mut String, b: u8) {
70 if b == b'\n' {
71 write!(s, "\\n").unwrap();
72 } else if b == b'\r' {
73 write!(s, "\\r").unwrap();
74 } else if b == b'\t' {
75 write!(s, "\\t").unwrap();
76 } else if b == b'\\' || b == b'"' {
77 write!(s, "\\{}", b as char).unwrap();
78 } else if b == b'\0' {
79 write!(s, "\\0").unwrap();
80 // ASCII printable except space
81 } else if b > 0x20 && b < 0x7f {
82 write!(s, "{}", b as char).unwrap();
83 } else {
84 write!(s, "\\x{:02x}", b).unwrap();
85 }
86}
87
88fn write_file_descriptor_data(
89 file: &FileDescriptorProto,
90 customize: &Customize,
91 w: &mut CodeWriter,
92) {
93 let fdp_bytes = file.write_to_bytes().unwrap();
94 w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\");
95 w.indented(|w| {
96 const MAX_LINE_LEN: usize = 72;
97
98 let mut s = String::new();
99 for &b in &fdp_bytes {
100 let prev_len = s.len();
101 escape_byte(&mut s, b);
102 let truncate = s.len() > MAX_LINE_LEN;
103 if truncate {
104 s.truncate(prev_len);
105 }
106 if truncate || s.len() == MAX_LINE_LEN {
107 write!(s, "\\").unwrap();
108 w.write_line(&s);
109 s.clear();
110 }
111 if truncate {
112 escape_byte(&mut s, b);
113 }
114 }
115 if !s.is_empty() {
116 write!(s, "\\").unwrap();
117 w.write_line(&s);
118 s.clear();
119 }
120 });
121 w.write_line("\";");
122 w.write_line("");
123 w.lazy_static_protobuf_path(
124 "file_descriptor_proto_lazy",
125 &format!(
126 "{}::descriptor::FileDescriptorProto",
127 protobuf_crate_path(customize)
128 ),
129 protobuf_crate_path(customize),
130 );
131 w.write_line("");
132 w.def_fn(
133 "parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto",
134 |w| {
135 w.write_line("::protobuf::parse_from_bytes(file_descriptor_proto_data).unwrap()");
136 },
137 );
138 w.write_line("");
139 w.pub_fn(
140 "file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto",
141 |w| {
Haibo Huang72fec012020-07-10 20:24:04 -0700142 w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| {
143 w.write_line("parse_descriptor_proto()");
Chih-Hung Hsieh92ff6052020-06-10 20:18:39 -0700144 });
145 },
146 );
147}
148
149fn gen_file(
150 file: &FileDescriptorProto,
151 _files_map: &HashMap<&str, &FileDescriptorProto>,
152 root_scope: &RootScope,
153 customize: &Customize,
154) -> Option<compiler_plugin::GenResult> {
155 // TODO: use it
156 let mut customize = customize.clone();
157 // options specified in invocation have precedence over options specified in file
158 customize.update_with(&customize_from_rustproto_for_file(file.get_options()));
159
160 let scope = FileScope {
161 file_descriptor: file,
162 }
163 .to_scope();
164 let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
165 file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME
166 });
167
168 let mut v = Vec::new();
169
170 {
171 let mut w = CodeWriter::new(&mut v);
172
173 // Hack: hard code version number here because Android.bp
174 // rust modules cannot pass it though env variable yet.
Haibo Huang72fec012020-07-10 20:24:04 -0700175 w.write_generated_by("rust-protobuf", "2.16.2");
Chih-Hung Hsieh92ff6052020-06-10 20:18:39 -0700176 w.write_line(&format!("//! Generated file from `{}`", file.get_name()));
Chih-Hung Hsieh92ff6052020-06-10 20:18:39 -0700177 if customize.inside_protobuf != Some(true) {
178 w.write_line("");
179 w.write_line("/// Generated files are compatible only with the same version");
180 w.write_line("/// of protobuf runtime.");
181 w.commented(|w| {
182 w.write_line(&format!(
183 "const _PROTOBUF_VERSION_CHECK: () = {}::{};",
184 protobuf_crate_path(&customize),
185 protobuf::VERSION_IDENT
186 ));
187 })
188 }
189
190 for message in &scope.get_messages() {
191 // ignore map entries, because they are not used in map fields
192 if message.map_entry().is_none() {
193 w.write_line("");
194 MessageGen::new(message, &root_scope, &customize).write(&mut w);
195 }
196 }
197 for enum_type in &scope.get_enums() {
198 w.write_line("");
199 EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w);
200 }
201
202 write_extensions(file, &root_scope, &mut w, &customize);
203
204 if !lite_runtime {
205 w.write_line("");
206 write_file_descriptor_data(file, &customize, &mut w);
207 }
208 }
209
210 Some(compiler_plugin::GenResult {
211 name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())),
212 content: v,
213 })
214}
215
216// This function is also used externally by cargo plugin
217// https://github.com/plietar/rust-protobuf-build
218// So be careful changing its signature.
219#[doc(hidden)]
220pub fn gen(
221 file_descriptors: &[FileDescriptorProto],
222 files_to_generate: &[String],
223 customize: &Customize,
224) -> Vec<compiler_plugin::GenResult> {
225 let root_scope = RootScope {
226 file_descriptors: file_descriptors,
227 };
228
229 let mut results: Vec<compiler_plugin::GenResult> = Vec::new();
230 let files_map: HashMap<&str, &FileDescriptorProto> =
231 file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
232
233 let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect();
234
235 for file_name in files_to_generate {
236 let file = files_map.get(&file_name[..]).expect(&format!(
237 "file not found in file descriptors: {:?}, files: {:?}",
238 file_name, all_file_names
239 ));
240 results.extend(gen_file(file, &files_map, &root_scope, customize));
241 }
242 results
243}
244
245#[doc(hidden)]
246pub fn gen_and_write(
247 file_descriptors: &[FileDescriptorProto],
248 files_to_generate: &[String],
249 out_dir: &Path,
250 customize: &Customize,
251) -> io::Result<()> {
252 let results = gen(file_descriptors, files_to_generate, customize);
253
254 for r in &results {
255 let mut file_path = out_dir.to_owned();
256 file_path.push(&r.name);
257 let mut file_writer = File::create(&file_path)?;
258 file_writer.write_all(&r.content)?;
259 file_writer.flush()?;
260 }
261
262 Ok(())
263}
264
265#[doc(hidden)]
266pub fn protoc_gen_rust_main() {
267 compiler_plugin::plugin_main_2(|r| {
268 let customize = Customize::parse_from_parameter(r.parameter).expect("parse options");
269 gen(r.file_descriptors, r.files_to_generate, &customize)
270 });
271}