Improve tonic-build configuration (#2)

This commit is contained in:
John Doneth
2019-09-23 09:32:44 -04:00
committed by Lucio Franco
parent 1c2905255e
commit eb0c0c286b
4 changed files with 137 additions and 58 deletions
-1
View File
@@ -12,7 +12,6 @@ syn = "1.0"
quote = "1.0" quote = "1.0"
proc-macro2 = "1.0" proc-macro2 = "1.0"
[features] [features]
default = ["transport"] default = ["transport"]
rustfmt = [] rustfmt = []
+132 -38
View File
@@ -2,52 +2,124 @@
//! and proto definitiones for use with `tonic`. //! and proto definitiones for use with `tonic`.
//! //!
//! # Examples //! # Examples
//! Simple
//! //!
//! ```rust,no_run //! ```rust,no_run
//! fn main() { //! fn main() {
//! tonic_build::compile_protos( //! tonic_build::compile_protos("proto/service.proto").unwrap();
//! &["proto/helloworld/helloworld.proto"],
//! &["proto/helloworld"],
//! "helloworld",
//! )
//! .unwrap();
//! } //! }
//! ```
//!
//! Configuration
//!
//! ```rust,no_run
//! fn main() {
//! tonic_build::configure()
//! .build_server(false)
//! .compile(
//! &["proto/helloworld/helloworld.proto"],
//! &["proto/helloworld"],
//! "helloworld",
//! )
//! .unwrap();
//! }
//! ```
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use prost_build::Config; use prost_build::Config;
#[cfg(feature = "rustfmt")] #[cfg(feature = "rustfmt")]
use std::process::Command; use std::process::Command;
use std::{io, path, path::Path}; use std::{
io,
path::{Path, PathBuf},
};
mod client; mod client;
mod service; mod service;
pub fn compile_protos<P>(protos: &[P], includes: &[P], package: &str) -> io::Result<()> #[derive(Clone)]
where pub struct Builder {
P: AsRef<path::Path>, build_client: bool,
{ build_server: bool,
let out_dir = std::env::var("OUT_DIR").unwrap(); out_dir: PathBuf,
compile_protos_with_out_dir(protos, includes, package, out_dir.as_str())
} }
#[allow(unused_variables)] impl Builder {
pub fn compile_protos_with_out_dir<P: AsRef<Path>>( /// Enable or disable gRPC client code generation.
protos: &[P], pub fn build_client(mut self, enable: bool) -> Self {
includes: &[P], self.build_client = enable;
package: &str, self
out_dir: impl AsRef<Path>, }
) -> io::Result<()> {
let mut config = Config::new();
config.service_generator(Box::new(ServiceGenerator::default())); /// Enable or disable gRPC server code generation.
config.out_dir(out_dir.as_ref()); pub fn build_server(mut self, enable: bool) -> Self {
config.compile_protos(protos, includes)?; self.build_server = enable;
self
}
#[cfg(feature = "rustfmt")] /// Set the output directory to generate code to.
fmt( ///
out_dir.as_ref().to_str().expect("Execpted utf8 out_dir"), /// Defaults to the `OUT_DIR` environment variable.
&format!("{}.rs", package), pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
); self.out_dir = out_dir.as_ref().to_path_buf();
self
}
/// Compile the .proto files and execute code generation.
#[cfg_attr(not(feature = "rustfmt"), allow(unused_variables))]
pub fn compile<P: AsRef<Path>>(
self,
protos: &[P],
includes: &[P],
package: &str,
) -> io::Result<()> {
let mut config = Config::new();
config.out_dir(self.out_dir.clone());
config.service_generator(Box::new(ServiceGenerator::new(self)));
config.compile_protos(protos, includes)?;
#[cfg(feature = "rustfmt")]
fmt(
out_dir.as_ref().to_str().expect("Execpted utf8 out_dir"),
&format!("{}.rs", package),
);
Ok(())
}
}
/// Configure tonic-build code generation.
///
/// Use [`compile_protos`] instead if you don't need to tweak anything.
pub fn configure() -> Builder {
Builder {
build_client: true,
build_server: true,
out_dir: PathBuf::from(std::env::var("OUT_DIR").unwrap()),
}
}
/// Simple `.proto` compiling. Use [`configure`] instead if you need more options.
///
/// The include directory will be the parent folder of the specified path.
/// The package name will be the filename without the extension.
pub fn compile_protos(proto_path: impl AsRef<Path>) -> io::Result<()> {
let proto_path: &Path = proto_path.as_ref();
let package = proto_path
.file_stem()
.expect("file should have a stem if it has an extension")
.to_str()
.expect("expected valid utf-8 filename");
// directory the main .proto file resides in
let proto_dir = proto_path
.parent()
.expect("proto file should reside in a directory");
self::configure().compile(&[proto_path], &[proto_dir], package)?;
Ok(()) Ok(())
} }
@@ -67,36 +139,58 @@ fn fmt(out_dir: &str, file: &str) {
assert!(out.status.success()); assert!(out.status.success());
} }
#[derive(Default)]
pub struct ServiceGenerator { pub struct ServiceGenerator {
builder: Builder,
clients: TokenStream, clients: TokenStream,
servers: TokenStream, servers: TokenStream,
} }
impl ServiceGenerator {
fn new(builder: Builder) -> Self {
ServiceGenerator {
builder,
clients: TokenStream::default(),
servers: TokenStream::default(),
}
}
}
impl prost_build::ServiceGenerator for ServiceGenerator { impl prost_build::ServiceGenerator for ServiceGenerator {
fn generate(&mut self, service: prost_build::Service, _buf: &mut String) { fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
let path = "super"; let path = "super";
let server = service::generate(&service, path); if self.builder.build_server {
self.servers.extend(server); let server = service::generate(&service, path);
self.servers.extend(server);
}
let client = client::generate(&service, path); if self.builder.build_client {
self.clients.extend(client); let client = client::generate(&service, path);
self.clients.extend(client);
}
} }
fn finalize(&mut self, buf: &mut String) { fn finalize(&mut self, buf: &mut String) {
if !self.clients.is_empty() && !self.servers.is_empty() { if self.builder.build_client && !self.clients.is_empty() {
let clients = &self.clients; let clients = &self.clients;
let servers = &self.servers;
let service = quote::quote! { let client_service = quote::quote! {
pub mod client { pub mod client {
#![allow(unused_variables, dead_code, missing_docs)] #![allow(unused_variables, dead_code, missing_docs)]
use tonic::codegen::*; use tonic::codegen::*;
#clients #clients
} }
};
let code = format!("{}", client_service);
buf.push_str(&code);
}
if self.builder.build_server && !self.servers.is_empty() {
let servers = &self.servers;
let server_service = quote::quote! {
pub mod server { pub mod server {
#![allow(unused_variables, dead_code, missing_docs)] #![allow(unused_variables, dead_code, missing_docs)]
use tonic::codegen::*; use tonic::codegen::*;
@@ -105,7 +199,7 @@ impl prost_build::ServiceGenerator for ServiceGenerator {
} }
}; };
let code = format!("{}", service); let code = format!("{}", server_service);
buf.push_str(&code); buf.push_str(&code);
} }
} }
+2 -13
View File
@@ -1,15 +1,4 @@
fn main() { fn main() {
tonic_build::compile_protos( tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap();
&["proto/helloworld/helloworld.proto"], tonic_build::compile_protos("proto/routeguide/route_guide.proto").unwrap();
&["proto/helloworld"],
"helloworld",
)
.unwrap();
tonic_build::compile_protos(
&["proto/routeguide/route_guide.proto"],
&["proto/routeguide"],
"routeguide",
)
.unwrap();
} }
+3 -6
View File
@@ -1,11 +1,8 @@
fn main() { fn main() {
let files = &["proto/grpc/testing/test.proto"]; let proto = "proto/grpc/testing/test.proto";
let dirs = &["proto/grpc/testing"];
tonic_build::compile_protos(files, dirs, "grpc.testing").unwrap(); tonic_build::compile_protos(proto).unwrap();
// prevent needing to rebuild if files (or deps) haven't changed // prevent needing to rebuild if files (or deps) haven't changed
for file in files { println!("cargo:rerun-if-changed={}", proto);
println!("cargo:rerun-if-changed={}", file);
}
} }