blob: 1f9128500234fbc439bdd547125dcdcbaa5cb12e [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| {
142 w.unsafe_expr(|w| {
143 w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| {
144 w.write_line("parse_descriptor_proto()");
145 });
146 });
147 },
148 );
149}
150
151fn gen_file(
152 file: &FileDescriptorProto,
153 _files_map: &HashMap<&str, &FileDescriptorProto>,
154 root_scope: &RootScope,
155 customize: &Customize,
156) -> Option<compiler_plugin::GenResult> {
157 // TODO: use it
158 let mut customize = customize.clone();
159 // options specified in invocation have precedence over options specified in file
160 customize.update_with(&customize_from_rustproto_for_file(file.get_options()));
161
162 let scope = FileScope {
163 file_descriptor: file,
164 }
165 .to_scope();
166 let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
167 file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME
168 });
169
170 let mut v = Vec::new();
171
172 {
173 let mut w = CodeWriter::new(&mut v);
174
175 // Hack: hard code version number here because Android.bp
176 // rust modules cannot pass it though env variable yet.
177 w.write_generated_by("rust-protobuf", "2.14.0");
178 w.write_line(&format!("//! Generated file from `{}`", file.get_name()));
179
180 w.write_line("");
181 w.write_line("use protobuf::Message as Message_imported_for_functions;");
182 w.write_line("use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions;");
183 if customize.inside_protobuf != Some(true) {
184 w.write_line("");
185 w.write_line("/// Generated files are compatible only with the same version");
186 w.write_line("/// of protobuf runtime.");
187 w.commented(|w| {
188 w.write_line(&format!(
189 "const _PROTOBUF_VERSION_CHECK: () = {}::{};",
190 protobuf_crate_path(&customize),
191 protobuf::VERSION_IDENT
192 ));
193 })
194 }
195
196 for message in &scope.get_messages() {
197 // ignore map entries, because they are not used in map fields
198 if message.map_entry().is_none() {
199 w.write_line("");
200 MessageGen::new(message, &root_scope, &customize).write(&mut w);
201 }
202 }
203 for enum_type in &scope.get_enums() {
204 w.write_line("");
205 EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w);
206 }
207
208 write_extensions(file, &root_scope, &mut w, &customize);
209
210 if !lite_runtime {
211 w.write_line("");
212 write_file_descriptor_data(file, &customize, &mut w);
213 }
214 }
215
216 Some(compiler_plugin::GenResult {
217 name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())),
218 content: v,
219 })
220}
221
222// This function is also used externally by cargo plugin
223// https://github.com/plietar/rust-protobuf-build
224// So be careful changing its signature.
225#[doc(hidden)]
226pub fn gen(
227 file_descriptors: &[FileDescriptorProto],
228 files_to_generate: &[String],
229 customize: &Customize,
230) -> Vec<compiler_plugin::GenResult> {
231 let root_scope = RootScope {
232 file_descriptors: file_descriptors,
233 };
234
235 let mut results: Vec<compiler_plugin::GenResult> = Vec::new();
236 let files_map: HashMap<&str, &FileDescriptorProto> =
237 file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
238
239 let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect();
240
241 for file_name in files_to_generate {
242 let file = files_map.get(&file_name[..]).expect(&format!(
243 "file not found in file descriptors: {:?}, files: {:?}",
244 file_name, all_file_names
245 ));
246 results.extend(gen_file(file, &files_map, &root_scope, customize));
247 }
248 results
249}
250
251#[doc(hidden)]
252pub fn gen_and_write(
253 file_descriptors: &[FileDescriptorProto],
254 files_to_generate: &[String],
255 out_dir: &Path,
256 customize: &Customize,
257) -> io::Result<()> {
258 let results = gen(file_descriptors, files_to_generate, customize);
259
260 for r in &results {
261 let mut file_path = out_dir.to_owned();
262 file_path.push(&r.name);
263 let mut file_writer = File::create(&file_path)?;
264 file_writer.write_all(&r.content)?;
265 file_writer.flush()?;
266 }
267
268 Ok(())
269}
270
271#[doc(hidden)]
272pub fn protoc_gen_rust_main() {
273 compiler_plugin::plugin_main_2(|r| {
274 let customize = Customize::parse_from_parameter(r.parameter).expect("parse options");
275 gen(r.file_descriptors, r.files_to_generate, &customize)
276 });
277}