protobuf_codegen/codegen/
mod.rs1use 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#[derive(Debug, Default)]
39pub struct Codegen {
40 which_parser: Option<WhichParser>,
42 create_out_dir: bool,
44 out_dir: Option<PathBuf>,
46 includes: Vec<PathBuf>,
48 inputs: Vec<PathBuf>,
50 customize: Customize,
52 customize_callback: CustomizeCallbackHolder,
54 protoc: Option<PathBuf>,
56 protoc_extra_args: Vec<OsString>,
58 capture_stderr: bool,
60}
61
62impl Codegen {
63 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn pure(&mut self) -> &mut Self {
74 self.which_parser = Some(WhichParser::Pure);
75 self
76 }
77
78 pub fn protoc(&mut self) -> &mut Self {
80 self.which_parser = Some(WhichParser::Protoc);
81 self
82 }
83
84 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 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 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 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 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 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 pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self {
177 self.protoc = Some(protoc.to_owned());
178 self
179 }
180
181 pub fn capture_stderr(&mut self) -> &mut Self {
183 self.capture_stderr = true;
184 self
185 }
186
187 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 pub fn customize(&mut self, customize: Customize) -> &mut Self {
199 self.customize.update_with(&customize);
200 self
201 }
202
203 pub fn customize_callback(&mut self, callback: impl CustomizeCallback) -> &mut Self {
205 self.customize_callback = CustomizeCallbackHolder::new(callback);
206 self
207 }
208
209 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 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}