protobuf_codegen/codegen/
mod.rs

1use std::env;
2use std::ffi::OsString;
3use std::fs;
4use std::path::Component;
5use std::path::Path;
6use std::path::PathBuf;
7use std::process;
8
9use anyhow::Context;
10use protobuf_parse::Parser;
11
12use crate::customize::CustomizeCallback;
13use crate::customize::CustomizeCallbackHolder;
14use crate::gen_and_write::gen_and_write;
15use crate::Customize;
16
17#[derive(Debug)]
18enum WhichParser {
19    Pure,
20    Protoc,
21}
22
23impl Default for WhichParser {
24    fn default() -> WhichParser {
25        WhichParser::Pure
26    }
27}
28
29#[derive(Debug, thiserror::Error)]
30enum CodegenError {
31    #[error("out_dir is not specified")]
32    OutDirNotSpecified,
33}
34
35/// Entry point for `.proto` to `.rs` code generation.
36///
37/// This is similar to `protoc --rs_out...`.
38#[derive(Debug, Default)]
39pub struct Codegen {
40    /// What parser to use to parse `.proto` files.
41    which_parser: Option<WhichParser>,
42    /// Create out directory.
43    create_out_dir: bool,
44    /// --lang_out= param
45    out_dir: Option<PathBuf>,
46    /// -I args
47    includes: Vec<PathBuf>,
48    /// List of .proto files to compile
49    inputs: Vec<PathBuf>,
50    /// Customize code generation
51    customize: Customize,
52    /// Customize code generation
53    customize_callback: CustomizeCallbackHolder,
54    /// Protoc command path
55    protoc: Option<PathBuf>,
56    /// Extra `protoc` args
57    protoc_extra_args: Vec<OsString>,
58    /// Capture stderr when running `protoc`.
59    capture_stderr: bool,
60}
61
62impl Codegen {
63    /// Create new codegen object.
64    ///
65    /// Uses `protoc` from `$PATH` by default.
66    ///
67    /// Can be switched to pure rust parser using [`pure`](Self::pure) function.
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Switch to pure Rust parser of `.proto` files.
73    pub fn pure(&mut self) -> &mut Self {
74        self.which_parser = Some(WhichParser::Pure);
75        self
76    }
77
78    /// Switch to `protoc` parser of `.proto` files.
79    pub fn protoc(&mut self) -> &mut Self {
80        self.which_parser = Some(WhichParser::Protoc);
81        self
82    }
83
84    /// Output directory for generated code.
85    ///
86    /// When invoking from `build.rs`, consider using
87    /// [`cargo_out_dir`](Self::cargo_out_dir) instead.
88    pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
89        self.out_dir = Some(out_dir.as_ref().to_owned());
90        self
91    }
92
93    /// Set output directory relative to Cargo output dir.
94    ///
95    /// With this option, output directory is erased and recreated during invocation.
96    pub fn cargo_out_dir(&mut self, rel: &str) -> &mut Self {
97        let rel = Path::new(rel);
98        let mut not_empty = false;
99        for comp in rel.components() {
100            match comp {
101                Component::ParentDir => {
102                    panic!("parent path in components of rel path: `{}`", rel.display());
103                }
104                Component::CurDir => {
105                    continue;
106                }
107                Component::Normal(..) => {}
108                Component::RootDir | Component::Prefix(..) => {
109                    panic!("root dir in components of rel path: `{}`", rel.display());
110                }
111            }
112            not_empty = true;
113        }
114
115        if !not_empty {
116            panic!("empty rel path: `{}`", rel.display());
117        }
118
119        let cargo_out_dir = env::var("OUT_DIR").expect("OUT_DIR env var not set");
120        let mut path = PathBuf::from(cargo_out_dir);
121        path.push(rel);
122        self.create_out_dir = true;
123        self.out_dir(path)
124    }
125
126    /// Add an include directory.
127    pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
128        self.includes.push(include.as_ref().to_owned());
129        self
130    }
131
132    /// Add include directories.
133    pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
134        for include in includes {
135            self.include(include);
136        }
137        self
138    }
139
140    /// Append a `.proto` file path to compile
141    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
142        self.inputs.push(input.as_ref().to_owned());
143        self
144    }
145
146    /// Append multiple `.proto` file paths to compile
147    pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
148        for input in inputs {
149            self.input(input);
150        }
151        self
152    }
153
154    /// Specify `protoc` command path to be used when invoking code generation.
155    ///
156    /// # Examples
157    ///
158    /// ```no_run
159    /// # mod protoc_bin_vendored {
160    /// #   pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> {
161    /// #       unimplemented!()
162    /// #   }
163    /// # }
164    ///
165    /// use protobuf_codegen::Codegen;
166    ///
167    /// Codegen::new()
168    ///     .protoc()
169    ///     .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap())
170    ///     // ...
171    ///     .run()
172    ///     .unwrap();
173    /// ```
174    ///
175    /// This option is ignored when pure Rust parser is used.
176    pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self {
177        self.protoc = Some(protoc.to_owned());
178        self
179    }
180
181    /// Capture stderr to error when running `protoc`.
182    pub fn capture_stderr(&mut self) -> &mut Self {
183        self.capture_stderr = true;
184        self
185    }
186
187    /// Extra command line flags for `protoc` invocation.
188    ///
189    /// For example, `--experimental_allow_proto3_optional` option.
190    ///
191    /// This option is ignored when pure Rust parser is used.
192    pub fn protoc_extra_arg(&mut self, arg: impl Into<OsString>) -> &mut Self {
193        self.protoc_extra_args.push(arg.into());
194        self
195    }
196
197    /// Set options to customize code generation
198    pub fn customize(&mut self, customize: Customize) -> &mut Self {
199        self.customize.update_with(&customize);
200        self
201    }
202
203    /// Callback for dynamic per-element customization.
204    pub fn customize_callback(&mut self, callback: impl CustomizeCallback) -> &mut Self {
205        self.customize_callback = CustomizeCallbackHolder::new(callback);
206        self
207    }
208
209    /// Invoke the code generation.
210    ///
211    /// This is roughly equivalent to `protoc --rs_out=...` but
212    /// without requiring `protoc-gen-rs` command in `$PATH`.
213    ///
214    /// This function uses pure Rust parser or `protoc` parser depending on
215    /// how this object was configured.
216    pub fn run(&self) -> anyhow::Result<()> {
217        let out_dir = match &self.out_dir {
218            Some(out_dir) => out_dir,
219            None => return Err(CodegenError::OutDirNotSpecified.into()),
220        };
221
222        if self.create_out_dir {
223            if out_dir.exists() {
224                fs::remove_dir_all(&out_dir)?;
225            }
226            fs::create_dir(&out_dir)?;
227        }
228
229        let mut parser = Parser::new();
230        parser.protoc();
231        if let Some(protoc) = &self.protoc {
232            parser.protoc_path(protoc);
233        }
234        match &self.which_parser {
235            Some(WhichParser::Protoc) => {
236                parser.protoc();
237            }
238            Some(WhichParser::Pure) => {
239                parser.pure();
240            }
241            None => {}
242        }
243
244        parser.inputs(&self.inputs);
245        parser.includes(&self.includes);
246        parser.protoc_extra_args(&self.protoc_extra_args);
247
248        if self.capture_stderr {
249            parser.capture_stderr();
250        }
251
252        let parsed_and_typechecked = parser
253            .parse_and_typecheck()
254            .context("parse and typecheck")?;
255
256        gen_and_write(
257            &parsed_and_typechecked.file_descriptors,
258            &parsed_and_typechecked.parser,
259            &parsed_and_typechecked.relative_paths,
260            &out_dir,
261            &self.customize,
262            &*self.customize_callback,
263        )
264    }
265
266    /// Similar to `run`, but prints the message to stderr and exits the process on error.
267    pub fn run_from_script(&self) {
268        if let Err(e) = self.run() {
269            eprintln!("codegen failed: {:?}", e);
270            process::exit(1);
271        }
272    }
273}