protoc_rust/
lib.rs

1//! # API to generate `.rs` files using `protoc` to parse files
2//!
3//! This API requires `protoc` command present in `$PATH`
4//! or explicitly passed to `Codegen` object
5//! (but `protoc` *plugin* is not needed).
6//!
7//! ```no_run
8//! extern crate protoc_rust;
9//!
10//! fn main() {
11//!     protoc_rust::Codegen::new()
12//!         .out_dir("src/protos")
13//!         .inputs(&["protos/a.proto", "protos/b.proto"])
14//!         .include("protos")
15//!         .run()
16//!         .expect("Running protoc failed.");
17//! }
18//! ```
19//!
20//! and in `build.rs`:
21//!
22//! ```toml
23//! [build-dependencies]
24//! protoc-rust = "2"
25//! ```
26//!
27//! It is advisable that `protoc-rust` build-dependency version be the same as
28//! `protobuf` dependency.
29//!
30//! The alternative is to use
31//! [`protobuf-codegen-pure` crate](https://docs.rs/protobuf-codegen-pure).
32//!
33//! # Protoc binary
34//!
35//! This crate searches for `protoc` binary in `$PATH` by default.
36//!
37//! `protoc` binary can be obtained using
38//! [`protoc-bin-vendored` crate](https://docs.rs/protoc-bin-vendored)
39//! and supplied to `Codegen` object.
40//!
41//! # This is version 2
42//!
43//! In branch 3 of rust-protobuf this functionality is provided by
44//! [`protobuf-codegen` crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha).
45
46#![deny(missing_docs)]
47#![deny(rustdoc::broken_intra_doc_links)]
48
49extern crate tempfile;
50
51extern crate protobuf;
52extern crate protobuf_codegen;
53extern crate protoc;
54
55mod slashes;
56use std::fs;
57use std::io;
58use std::io::Read;
59use std::path::Path;
60use std::path::PathBuf;
61
62use protobuf::descriptor::FileDescriptorSet;
63use protobuf::Message;
64pub use protobuf_codegen::Customize;
65pub use protoc::Error;
66use protoc::Protoc;
67pub use protoc::Result;
68use slashes::Slashes;
69
70/// `Protoc --rust_out...` args
71#[derive(Debug, Default)]
72#[deprecated(since = "2.14", note = "Use Codegen instead")]
73pub struct Args<'a> {
74    /// --lang_out= param
75    pub out_dir: &'a str,
76    /// -I args
77    pub includes: &'a [&'a str],
78    /// List of .proto files to compile
79    pub input: &'a [&'a str],
80    /// Customize code generation
81    pub customize: Customize,
82}
83
84/// `Protoc --rust_out...` args
85#[derive(Debug, Default)]
86pub struct Codegen {
87    /// --lang_out= param
88    out_dir: PathBuf,
89    /// -I args
90    includes: Vec<PathBuf>,
91    /// List of .proto files to compile
92    inputs: Vec<PathBuf>,
93    /// Customize code generation
94    customize: Customize,
95    /// Protoc command path
96    protoc: Option<Protoc>,
97}
98
99impl Codegen {
100    /// Arguments to the `protoc` found in `$PATH`
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    /// Set `--LANG_out=...` param
106    pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
107        self.out_dir = out_dir.as_ref().to_owned();
108        self
109    }
110
111    /// Append a path to `-I` args
112    pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
113        self.includes.push(include.as_ref().to_owned());
114        self
115    }
116
117    /// Append multiple paths to `-I` args
118    pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
119        for include in includes {
120            self.include(include);
121        }
122        self
123    }
124
125    /// Append a `.proto` file path to compile
126    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
127        self.inputs.push(input.as_ref().to_owned());
128        self
129    }
130
131    /// Append multiple `.proto` file paths to compile
132    pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
133        for input in inputs {
134            self.input(input);
135        }
136        self
137    }
138
139    /// Specify `protoc` command path to be used when invoking code generation.
140    ///
141    /// # Examples
142    ///
143    /// ```no_run
144    /// # mod protoc_bin_vendored {
145    /// #   pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> {
146    /// #       unimplemented!()
147    /// #   }
148    /// # }
149    ///
150    /// use protoc_rust::Codegen;
151    ///
152    /// Codegen::new()
153    ///     .protoc_path(protoc_bin_vendored::protoc_bin_path().unwrap())
154    ///     // ...
155    ///     .run()
156    ///     .unwrap();
157    /// ```
158    pub fn protoc_path(&mut self, protoc: impl Into<PathBuf>) -> &mut Self {
159        self.protoc = Some(Protoc::from_path(&protoc.into().to_str().unwrap()));
160        self
161    }
162
163    /// Set options to customize code generation
164    pub fn customize(&mut self, customize: Customize) -> &mut Self {
165        self.customize = customize;
166        self
167    }
168
169    /// Like `protoc --rust_out=...` but without requiring `protoc-gen-rust` command in `$PATH`.
170    pub fn run(&self) -> Result<()> {
171        let protoc = match self.protoc.clone() {
172            Some(protoc) => protoc,
173            None => Protoc::from_env_path(),
174        };
175        protoc.check()?;
176
177        let temp_dir = tempfile::Builder::new().prefix("protoc-rust").tempdir()?;
178        let temp_file = temp_dir.path().join("descriptor.pbbin");
179
180        let includes: Vec<&str> = self.includes.iter().map(|p| p.to_str().unwrap()).collect();
181        let inputs: Vec<&str> = self.inputs.iter().map(|p| p.to_str().unwrap()).collect();
182
183        protoc.write_descriptor_set(protoc::DescriptorSetOutArgs {
184            out: temp_file.as_os_str().to_str().unwrap(),
185            includes: &includes,
186            input: &inputs,
187            include_imports: true,
188        })?;
189
190        let mut fds = Vec::new();
191        let mut file = fs::File::open(temp_file)?;
192        file.read_to_end(&mut fds)?;
193
194        drop(file);
195        drop(temp_dir);
196
197        let fds = FileDescriptorSet::parse_from_bytes(&fds)
198            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
199
200        let default_includes = vec![PathBuf::from(".")];
201        let includes = if self.includes.is_empty() {
202            &default_includes
203        } else {
204            &self.includes
205        };
206
207        let mut files_to_generate = Vec::new();
208        'outer: for file in &self.inputs {
209            for include in includes {
210                if let Some(truncated) =
211                    remove_path_prefix(file.to_str().unwrap(), include.to_str().unwrap())
212                {
213                    files_to_generate.push(truncated.to_owned());
214                    continue 'outer;
215                }
216            }
217
218            return Err(Error::new(
219                io::ErrorKind::Other,
220                format!("file {:?} is not found in includes {:?}", file, includes),
221            ));
222        }
223
224        protobuf_codegen::gen_and_write(
225            fds.get_file(),
226            &files_to_generate,
227            &self.out_dir,
228            &self.customize,
229        )
230    }
231}
232
233/// Like `protoc --rust_out=...` but without requiring `protoc-gen-rust` command in `$PATH`.
234#[deprecated(since = "2.14", note = "Use Codegen instead")]
235#[allow(deprecated)]
236pub fn run(args: Args) -> Result<()> {
237    Codegen::new()
238        .out_dir(args.out_dir)
239        .includes(args.includes)
240        .inputs(args.input)
241        .customize(args.customize)
242        .run()
243}
244
245fn remove_path_prefix(mut path: &str, mut prefix: &str) -> Option<String> {
246    let slashes = Slashes::here();
247    path = slashes.remove_dot_slashes(path);
248    prefix = slashes.remove_dot_slashes(prefix);
249
250    if prefix == "" {
251        return Some(path.to_owned());
252    }
253
254    let path = slashes.norm_path(path);
255    let mut prefix = slashes.norm_path(prefix);
256
257    if prefix.ends_with("/") {
258        let l = prefix.len();
259        prefix.truncate(l - 1);
260    }
261
262    if !path.starts_with(&prefix) {
263        return None;
264    }
265
266    if path.len() <= prefix.len() {
267        return None;
268    }
269
270    if path.as_bytes()[prefix.len()] == b'/' {
271        return Some(path[prefix.len() + 1..].to_owned());
272    } else {
273        return None;
274    }
275}
276
277#[cfg(test)]
278mod test {
279    #[test]
280    fn remove_path_prefix() {
281        assert_eq!(
282            Some("abc.proto".to_owned()),
283            super::remove_path_prefix("xxx/abc.proto", "xxx")
284        );
285        assert_eq!(
286            Some("abc.proto".to_owned()),
287            super::remove_path_prefix("xxx/abc.proto", "xxx/")
288        );
289        assert_eq!(
290            Some("abc.proto".to_owned()),
291            super::remove_path_prefix("../xxx/abc.proto", "../xxx/")
292        );
293        assert_eq!(
294            Some("abc.proto".to_owned()),
295            super::remove_path_prefix("abc.proto", ".")
296        );
297        assert_eq!(
298            Some("abc.proto".to_owned()),
299            super::remove_path_prefix("abc.proto", "./")
300        );
301        assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy"));
302        assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy/"));
303    }
304}