1 use std::env; 2 use std::ffi::OsString; 3 use std::fs; 4 use std::path::Component; 5 use std::path::Path; 6 use std::path::PathBuf; 7 use std::process; 8 9 use anyhow::Context; 10 use protobuf_parse::Parser; 11 12 use crate::customize::CustomizeCallback; 13 use crate::customize::CustomizeCallbackHolder; 14 use crate::gen_and_write::gen_and_write; 15 use crate::Customize; 16 17 #[derive(Debug)] 18 enum WhichParser { 19 Pure, 20 Protoc, 21 } 22 23 impl Default for WhichParser { default() -> WhichParser24 fn default() -> WhichParser { 25 WhichParser::Pure 26 } 27 } 28 29 #[derive(Debug, thiserror::Error)] 30 enum 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 --rust_out...`. 38 #[derive(Debug, Default)] 39 pub 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 62 impl 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. new() -> Self68 pub fn new() -> Self { 69 Self::default() 70 } 71 72 /// Switch to pure Rust parser of `.proto` files. pure(&mut self) -> &mut Self73 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. protoc(&mut self) -> &mut Self79 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. out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self88 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. cargo_out_dir(&mut self, rel: &str) -> &mut Self96 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. include(&mut self, include: impl AsRef<Path>) -> &mut Self127 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. includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self133 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 input(&mut self, input: impl AsRef<Path>) -> &mut Self141 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 inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self147 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. protoc_path(&mut self, protoc: &Path) -> &mut Self176 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`. capture_stderr(&mut self) -> &mut Self182 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. protoc_extra_arg(&mut self, arg: impl Into<OsString>) -> &mut Self192 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 customize(&mut self, customize: Customize) -> &mut Self198 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. customize_callback(&mut self, callback: impl CustomizeCallback) -> &mut Self204 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 --rust_out=...` but 212 /// without requiring `protoc-gen-rust` command in `$PATH`. 213 /// 214 /// This function uses pure Rust parser or `protoc` parser depending on 215 /// how this object was configured. run(&self) -> anyhow::Result<()>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 247 if self.capture_stderr { 248 parser.capture_stderr(); 249 } 250 251 let parsed_and_typechecked = parser 252 .parse_and_typecheck() 253 .context("parse and typecheck")?; 254 255 gen_and_write( 256 &parsed_and_typechecked.file_descriptors, 257 &parsed_and_typechecked.parser, 258 &parsed_and_typechecked.relative_paths, 259 &out_dir, 260 &self.customize, 261 &*self.customize_callback, 262 ) 263 } 264 265 /// Similar to `run`, but prints the message to stderr and exits the process on error. run_from_script(&self)266 pub fn run_from_script(&self) { 267 if let Err(e) = self.run() { 268 eprintln!("codegen failed: {:?}", e); 269 process::exit(1); 270 } 271 } 272 } 273