| use std::env; |
| use std::ffi::OsString; |
| use std::fs; |
| use std::path::Component; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::process; |
| |
| use anyhow::Context; |
| use protobuf_parse::Parser; |
| |
| use crate::customize::CustomizeCallback; |
| use crate::customize::CustomizeCallbackHolder; |
| use crate::gen_and_write::gen_and_write; |
| use crate::Customize; |
| |
| #[derive(Debug)] |
| enum WhichParser { |
| Pure, |
| Protoc, |
| } |
| |
| impl Default for WhichParser { |
| fn default() -> WhichParser { |
| WhichParser::Pure |
| } |
| } |
| |
| #[derive(Debug, thiserror::Error)] |
| enum CodegenError { |
| #[error("out_dir is not specified")] |
| OutDirNotSpecified, |
| } |
| |
| /// Entry point for `.proto` to `.rs` code generation. |
| /// |
| /// This is similar to `protoc --rust_out...`. |
| #[derive(Debug, Default)] |
| pub struct Codegen { |
| /// What parser to use to parse `.proto` files. |
| which_parser: Option<WhichParser>, |
| /// Create out directory. |
| create_out_dir: bool, |
| /// --lang_out= param |
| out_dir: Option<PathBuf>, |
| /// -I args |
| includes: Vec<PathBuf>, |
| /// List of .proto files to compile |
| inputs: Vec<PathBuf>, |
| /// Customize code generation |
| customize: Customize, |
| /// Customize code generation |
| customize_callback: CustomizeCallbackHolder, |
| /// Protoc command path |
| protoc: Option<PathBuf>, |
| /// Extra `protoc` args |
| protoc_extra_args: Vec<OsString>, |
| /// Capture stderr when running `protoc`. |
| capture_stderr: bool, |
| } |
| |
| impl Codegen { |
| /// Create new codegen object. |
| /// |
| /// Uses `protoc` from `$PATH` by default. |
| /// |
| /// Can be switched to pure rust parser using [`pure`](Self::pure) function. |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Switch to pure Rust parser of `.proto` files. |
| pub fn pure(&mut self) -> &mut Self { |
| self.which_parser = Some(WhichParser::Pure); |
| self |
| } |
| |
| /// Switch to `protoc` parser of `.proto` files. |
| pub fn protoc(&mut self) -> &mut Self { |
| self.which_parser = Some(WhichParser::Protoc); |
| self |
| } |
| |
| /// Output directory for generated code. |
| /// |
| /// When invoking from `build.rs`, consider using |
| /// [`cargo_out_dir`](Self::cargo_out_dir) instead. |
| pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self { |
| self.out_dir = Some(out_dir.as_ref().to_owned()); |
| self |
| } |
| |
| /// Set output directory relative to Cargo output dir. |
| /// |
| /// With this option, output directory is erased and recreated during invocation. |
| pub fn cargo_out_dir(&mut self, rel: &str) -> &mut Self { |
| let rel = Path::new(rel); |
| let mut not_empty = false; |
| for comp in rel.components() { |
| match comp { |
| Component::ParentDir => { |
| panic!("parent path in components of rel path: `{}`", rel.display()); |
| } |
| Component::CurDir => { |
| continue; |
| } |
| Component::Normal(..) => {} |
| Component::RootDir | Component::Prefix(..) => { |
| panic!("root dir in components of rel path: `{}`", rel.display()); |
| } |
| } |
| not_empty = true; |
| } |
| |
| if !not_empty { |
| panic!("empty rel path: `{}`", rel.display()); |
| } |
| |
| let cargo_out_dir = env::var("OUT_DIR").expect("OUT_DIR env var not set"); |
| let mut path = PathBuf::from(cargo_out_dir); |
| path.push(rel); |
| self.create_out_dir = true; |
| self.out_dir(path) |
| } |
| |
| /// Add an include directory. |
| pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self { |
| self.includes.push(include.as_ref().to_owned()); |
| self |
| } |
| |
| /// Add include directories. |
| pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self { |
| for include in includes { |
| self.include(include); |
| } |
| self |
| } |
| |
| /// Append a `.proto` file path to compile |
| pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self { |
| self.inputs.push(input.as_ref().to_owned()); |
| self |
| } |
| |
| /// Append multiple `.proto` file paths to compile |
| pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self { |
| for input in inputs { |
| self.input(input); |
| } |
| self |
| } |
| |
| /// Specify `protoc` command path to be used when invoking code generation. |
| /// |
| /// # Examples |
| /// |
| /// ```no_run |
| /// # mod protoc_bin_vendored { |
| /// # pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> { |
| /// # unimplemented!() |
| /// # } |
| /// # } |
| /// |
| /// use protobuf_codegen::Codegen; |
| /// |
| /// Codegen::new() |
| /// .protoc() |
| /// .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) |
| /// // ... |
| /// .run() |
| /// .unwrap(); |
| /// ``` |
| /// |
| /// This option is ignored when pure Rust parser is used. |
| pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self { |
| self.protoc = Some(protoc.to_owned()); |
| self |
| } |
| |
| /// Capture stderr to error when running `protoc`. |
| pub fn capture_stderr(&mut self) -> &mut Self { |
| self.capture_stderr = true; |
| self |
| } |
| |
| /// Extra command line flags for `protoc` invocation. |
| /// |
| /// For example, `--experimental_allow_proto3_optional` option. |
| /// |
| /// This option is ignored when pure Rust parser is used. |
| pub fn protoc_extra_arg(&mut self, arg: impl Into<OsString>) -> &mut Self { |
| self.protoc_extra_args.push(arg.into()); |
| self |
| } |
| |
| /// Set options to customize code generation |
| pub fn customize(&mut self, customize: Customize) -> &mut Self { |
| self.customize.update_with(&customize); |
| self |
| } |
| |
| /// Callback for dynamic per-element customization. |
| pub fn customize_callback(&mut self, callback: impl CustomizeCallback) -> &mut Self { |
| self.customize_callback = CustomizeCallbackHolder::new(callback); |
| self |
| } |
| |
| /// Invoke the code generation. |
| /// |
| /// This is roughly equivalent to `protoc --rust_out=...` but |
| /// without requiring `protoc-gen-rust` command in `$PATH`. |
| /// |
| /// This function uses pure Rust parser or `protoc` parser depending on |
| /// how this object was configured. |
| pub fn run(&self) -> anyhow::Result<()> { |
| let out_dir = match &self.out_dir { |
| Some(out_dir) => out_dir, |
| None => return Err(CodegenError::OutDirNotSpecified.into()), |
| }; |
| |
| if self.create_out_dir { |
| if out_dir.exists() { |
| fs::remove_dir_all(&out_dir)?; |
| } |
| fs::create_dir(&out_dir)?; |
| } |
| |
| let mut parser = Parser::new(); |
| parser.protoc(); |
| if let Some(protoc) = &self.protoc { |
| parser.protoc_path(protoc); |
| } |
| match &self.which_parser { |
| Some(WhichParser::Protoc) => { |
| parser.protoc(); |
| } |
| Some(WhichParser::Pure) => { |
| parser.pure(); |
| } |
| None => {} |
| } |
| |
| parser.inputs(&self.inputs); |
| parser.includes(&self.includes); |
| |
| if self.capture_stderr { |
| parser.capture_stderr(); |
| } |
| |
| let parsed_and_typechecked = parser |
| .parse_and_typecheck() |
| .context("parse and typecheck")?; |
| |
| gen_and_write( |
| &parsed_and_typechecked.file_descriptors, |
| &parsed_and_typechecked.parser, |
| &parsed_and_typechecked.relative_paths, |
| &out_dir, |
| &self.customize, |
| &*self.customize_callback, |
| ) |
| } |
| |
| /// Similar to `run`, but prints the message to stderr and exits the process on error. |
| pub fn run_from_script(&self) { |
| if let Err(e) = self.run() { |
| eprintln!("codegen failed: {:?}", e); |
| process::exit(1); |
| } |
| } |
| } |