From 89187be2c2a67aaec4b1f2e6eca7c3558889b12b Mon Sep 17 00:00:00 2001 From: Tuetuopay Date: Fri, 25 Jun 2021 09:02:14 +0000 Subject: [PATCH] build: Add option to generate a full crate --- prost-build/src/lib.rs | 71 ++++++++++++++++++++++++++++---- prost-build/src/lib_generator.rs | 39 +++++++++++++++++- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 4cf0518bf..e01ff2f4f 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -117,7 +117,7 @@ mod lib_generator; mod message_graph; mod path; -use std::collections::HashMap; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::default; use std::env; use std::ffi::{OsStr, OsString}; @@ -228,7 +228,8 @@ const DISABLE_COMMENTS: &str = "disable_comments"; const EXTERN_PATH: &str = "extern_path"; const RETAIN_ENUM_PREFIX: &str = "retain_enum_prefix"; const MOD_RS: &str = "mod_rs"; -pub const PROTOC_OPTS: [&str; 9] = [ +const GEN_CRATE: &str = "gen_crate"; +pub const PROTOC_OPTS: [&str; 10] = [ BTREE_MAP, BYTES, FIELD_ATTR, @@ -238,6 +239,7 @@ pub const PROTOC_OPTS: [&str; 9] = [ EXTERN_PATH, RETAIN_ENUM_PREFIX, MOD_RS, + GEN_CRATE, ]; /// Configuration options for Protobuf code generation. @@ -257,6 +259,7 @@ pub struct Config { protoc_args: Vec, disable_comments: PathMap<()>, mod_rs: bool, + manifest_tpl: Option, } impl Config { @@ -292,6 +295,7 @@ impl Config { [EXTERN_PATH, k, v] => self.extern_paths.push((k.to_string(), v.to_string())), [RETAIN_ENUM_PREFIX] => self.strip_enum_prefix = false, [MOD_RS] => self.mod_rs = true, + [GEN_CRATE, v] => self.manifest_tpl = Some(v.into()), _ if log_unknown => eprintln!("prost: Unknown option `{}`", opt.join("=")), _ => (), } @@ -745,6 +749,20 @@ impl Config { self } + /// Generate a full crate based on a `Cargo.toml` template. + /// + /// The output directory will contain the crate and not the built files directly, in `gen/` + /// folder. Each `proto` package will get its own feature flag, with proper dependency solving. + /// The `Cargo.toml` must contain `{{ features }}` string where all crate features will be + /// inserted. + pub fn generate_crate

(&mut self, manifest_template: P) -> &mut Self + where + P: Into, + { + self.manifest_tpl = Some(manifest_template.into()); + self + } + /// Compile `.proto` files into Rust files during a Cargo build with additional code generator /// configuration options. /// @@ -836,8 +854,7 @@ impl Config { let modules = self.generate(file_descriptor_set.file)?; for (module, content) in modules { - let mut filename = module.join("."); - filename.push_str(".rs"); + let filename = self.filename(&module); let output_path = target.join(&filename); @@ -863,24 +880,39 @@ impl Config { match self.generate(req.proto_file) { Ok(modules) => { let f = modules.into_iter().map(|(module, content)| File { - name: Some(module.join(".") + ".rs"), + name: Some(self.filename(&module)), insertion_point: None, content: Some(content), generated_code_info: None, }); - CodeGeneratorResponse { error: None, supported_features: None, file: f.collect() } + CodeGeneratorResponse { + error: None, + supported_features: None, + file: f.collect(), + } } Err(e) => CodeGeneratorResponse { error: Some(e.to_string()), supported_features: None, file: Vec::new(), - } + }, } } + fn filename(&self, module: &Module) -> String { + let (prefix, suffix) = match (self.manifest_tpl.is_some(), module.as_slice()) { + (true, [n]) if n == "lib" => ("src/", ".rs"), + (true, [n]) if n == "Cargo.toml" => ("", ""), + (true, _) => ("gen/", ".rs"), + (false, _) => ("", ".rs"), + }; + prefix.to_owned() + &module.join(".") + suffix + } + fn generate(&mut self, files: Vec) -> Result> { let mut modules = HashMap::new(); let mut packages = HashMap::new(); + let deps = self.manifest_tpl.is_some().then(|| self.build_deps(&files)).unwrap_or_default(); let message_graph = MessageGraph::new(&files) .map_err(|error| Error::new(ErrorKind::InvalidInput, error))?; @@ -906,14 +938,19 @@ impl Config { } } - if self.mod_rs { + if self.mod_rs || self.manifest_tpl.is_some() { let mut mods = Mod::default(); for (module, _) in &modules { mods.push(module); } - let mut buf = modules.entry(vec!["mod".to_owned()]).or_default(); + let name = self.manifest_tpl.is_some().then(|| "lib").unwrap_or("mod"); + let mut buf = modules.entry(vec![name.to_owned()]).or_default(); LibGenerator::generate_librs(self, &mods, &mut buf); } + if let Some(tpl) = self.manifest_tpl.clone() { + let mut buf = modules.entry(vec!["Cargo.toml".to_owned()]).or_default(); + LibGenerator::generate_manifest(self, tpl, deps, &mut buf); + } Ok(modules) } @@ -925,6 +962,21 @@ impl Config { .map(to_snake) .collect() } + + fn build_deps(&self, files: &[FileDescriptorProto]) -> BTreeMap> { + let names: HashMap<_, _> = + files.iter().map(|file| (file.name().to_owned(), file.package().to_owned())).collect(); + let mut deps: BTreeMap<_, BTreeSet<_>> = BTreeMap::new(); + for file in files { + let names = file.dependency + .iter() + .filter_map(|dep| names.get(dep)) + .filter(|dep| *dep != file.package()) + .map(|s| s.clone()); + deps.entry(file.package().to_owned()).or_default().extend(names); + } + deps + } } impl default::Default for Config { @@ -943,6 +995,7 @@ impl default::Default for Config { protoc_args: Vec::new(), disable_comments: PathMap::default(), mod_rs: false, + manifest_tpl: None, } } } diff --git a/prost-build/src/lib_generator.rs b/prost-build/src/lib_generator.rs index 753b89c83..94dc13fa4 100644 --- a/prost-build/src/lib_generator.rs +++ b/prost-build/src/lib_generator.rs @@ -1,4 +1,6 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::read_to_string; +use std::path::PathBuf; use crate::{Config, Module}; @@ -57,8 +59,19 @@ impl<'a> LibGenerator<'a> { } for package in mods.contents.iter().map(|content| content.join(".")) { + if self.config.manifest_tpl.is_some() { + let feature = package.replace("r#", "").replace(".", "_"); + self.push_indent(); + self.buf.push_str("#[cfg(feature = \""); + self.buf.push_str(&feature); + self.buf.push_str("\")]\n"); + } + self.push_indent(); self.buf.push_str("include!(\""); + if self.config.manifest_tpl.is_some() { + self.buf.push_str("../gen/"); + } self.buf.push_str(&package); self.buf.push_str(".rs\");\n"); } @@ -69,4 +82,28 @@ impl<'a> LibGenerator<'a> { self.buf.push_str(" "); } } + + pub fn generate_manifest( + config: &'a mut Config, + template: PathBuf, + deps: BTreeMap>, + buf: &mut String, + ) { + let mut generator = LibGenerator { config, depth: 0, buf }; + generator.push_manifest(template, deps); + } + + fn push_manifest(&mut self, template: PathBuf, deps: BTreeMap>) { + let template = read_to_string(template).unwrap(); + let mut buf = String::new(); + for (feat, deps) in deps { + buf.push('"'); + buf.push_str(&feat); + buf.push_str("\" = ["); + let deps: Vec<_> = deps.iter().map(|dep| format!("\"{}\"", dep)).collect(); + buf.push_str(&deps.join(", ")); + buf.push_str("]\n"); + } + self.buf.push_str(&template.replace("{{ features }}", &buf)); + } }