use std::io::Write; use walkdir::WalkDir; fn main() -> Result<(), Box> { let file_paths = get_proto_file_paths()?; compile_protobuf(std::env::current_dir()?.display().to_string(), file_paths)?; let build_path = std::env::var_os("OUT_DIR").unwrap(); let publish_under_package = "com.oliverhines.friendbook.grpc.services."; let code = generate_module_code(build_path.to_str().unwrap(), publish_under_package); let mut file = std::fs::File::create(vec![build_path.to_str().unwrap(), "services.rs"].join("/"))?; file.write_all(code?.as_bytes())?; Ok(()) } /// Compiles given set of `.proto` files to Rust using tonic_build crate. /// * `file_path_root` should be absolute path that `file_paths` are given relative to /// * `file_paths` set of relative paths from the absolute `file_paths` fn compile_protobuf(file_path_root: String, file_paths: Vec) -> std::io::Result<()> { let proto_path_arg: Vec = vec![String::from("--proto_path="), file_path_root]; return tonic_build::configure() .protoc_arg(proto_path_arg.join("")) .build_server(true) .compile(&file_paths, &[]); } /// Returns the relative paths for every `.proto` file under "proto" directory fn get_proto_file_paths() -> std::io::Result> { let mut file_paths = Vec::new(); for entry in WalkDir::new("proto") { let entry = entry?; let file_name = entry.path().display().to_string(); if entry.file_type().is_file() && file_name.ends_with(".proto") { file_paths.push(file_name); println!("{}", entry.path().display().to_string()) } } return Ok(file_paths); } fn generate_module_code(build_path: &str, publish_under_package: &str) -> std::io::Result { let mut scope = codegen::Scope::new(); let top_level_mod = scope.new_module("services"); top_level_mod.vis("pub"); for entry in WalkDir::new(&build_path) { let entry = entry?; let file_name = entry.file_name().to_str().unwrap(); if entry.file_type().is_file() && str::starts_with(file_name, publish_under_package) && str::ends_with(file_name, ".rs") { let (_, remaining) = str::split_at(file_name, publish_under_package.len()); let (remaining, _) = str::split_at(remaining, remaining.len() - 3); let package_name_parts: Vec<&str> = remaining.split(".").collect(); let mut current_mod = &mut *top_level_mod; for i in 0..package_name_parts.len() { current_mod = current_mod.get_or_new_module(package_name_parts[i]); current_mod.vis("pub"); } current_mod.new_struct(file_name); } } let mut scope_to_change = scope.to_string().replace( "struct ", "include!(concat!( env!(\"OUT_DIR\"), \"/", ); scope_to_change = scope_to_change.replace(";", "\"));"); return Ok(scope_to_change); }