From 2e082f8b3eea3a3c4395eaa846b004d06b7061d5 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Sun, 29 Mar 2020 12:57:11 -0400 Subject: [PATCH] chore(build): Refactor codegen traits (#302) Signed-off-by: Lucio Franco --- examples/build.rs | 8 +-- examples/routeguide-tutorial.md | 2 +- interop/build.rs | 2 +- tests/extern_path/my_application/build.rs | 2 +- tests/included_service/build.rs | 2 +- tests/same_name/build.rs | 2 +- tests/wellknown/build.rs | 2 +- tonic-build/README.md | 4 +- tonic-build/src/client.rs | 56 +++++++----------- tonic-build/src/lib.rs | 10 +++- tonic-build/src/prost.rs | 70 +++++++++++------------ tonic-build/src/schema.rs | 35 +++++------- tonic-build/src/server.rs | 62 ++++++++++---------- 13 files changed, 113 insertions(+), 144 deletions(-) diff --git a/examples/build.rs b/examples/build.rs index f5382a2..97ab2d0 100644 --- a/examples/build.rs +++ b/examples/build.rs @@ -1,6 +1,6 @@ fn main() { - tonic_build::prost::compile_protos("proto/helloworld/helloworld.proto").unwrap(); - tonic_build::prost::compile_protos("proto/routeguide/route_guide.proto").unwrap(); - tonic_build::prost::compile_protos("proto/echo/echo.proto").unwrap(); - tonic_build::prost::compile_protos("proto/google/pubsub/pubsub.proto").unwrap(); + tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap(); + tonic_build::compile_protos("proto/routeguide/route_guide.proto").unwrap(); + tonic_build::compile_protos("proto/echo/echo.proto").unwrap(); + tonic_build::compile_protos("proto/google/pubsub/pubsub.proto").unwrap(); } diff --git a/examples/routeguide-tutorial.md b/examples/routeguide-tutorial.md index a4a9fe1..ffa1bd4 100644 --- a/examples/routeguide-tutorial.md +++ b/examples/routeguide-tutorial.md @@ -823,7 +823,7 @@ opposed to at build time, placing the resulting modules wherever we need them. ```rust fn main() { - tonic_build::prost::configure() + tonic_build::configure() .build_client(false) .out_dir("another_crate/src/pb") .compile(&["path/my_proto.proto"], &["path"]) diff --git a/interop/build.rs b/interop/build.rs index fbc5dcc..7783987 100644 --- a/interop/build.rs +++ b/interop/build.rs @@ -1,7 +1,7 @@ fn main() { let proto = "proto/grpc/testing/test.proto"; - tonic_build::prost::compile_protos(proto).unwrap(); + tonic_build::compile_protos(proto).unwrap(); // prevent needing to rebuild if files (or deps) haven't changed println!("cargo:rerun-if-changed={}", proto); diff --git a/tests/extern_path/my_application/build.rs b/tests/extern_path/my_application/build.rs index 893b392..5d25b01 100644 --- a/tests/extern_path/my_application/build.rs +++ b/tests/extern_path/my_application/build.rs @@ -1,5 +1,5 @@ fn main() -> Result<(), std::io::Error> { - tonic_build::prost::configure() + tonic_build::configure() .build_server(false) .build_client(true) .extern_path(".uuid", "::uuid") diff --git a/tests/included_service/build.rs b/tests/included_service/build.rs index b7aeeae..1c29065 100644 --- a/tests/included_service/build.rs +++ b/tests/included_service/build.rs @@ -1,3 +1,3 @@ fn main() { - tonic_build::prost::compile_protos("proto/includer.proto").unwrap(); + tonic_build::compile_protos("proto/includer.proto").unwrap(); } diff --git a/tests/same_name/build.rs b/tests/same_name/build.rs index 82de4ee..ba5ab11 100644 --- a/tests/same_name/build.rs +++ b/tests/same_name/build.rs @@ -1,3 +1,3 @@ fn main() { - tonic_build::prost::compile_protos("proto/foo.proto").unwrap(); + tonic_build::compile_protos("proto/foo.proto").unwrap(); } diff --git a/tests/wellknown/build.rs b/tests/wellknown/build.rs index 7f0a3c5..b3952da 100644 --- a/tests/wellknown/build.rs +++ b/tests/wellknown/build.rs @@ -1,3 +1,3 @@ fn main() { - tonic_build::prost::compile_protos("proto/wellknown.proto").unwrap(); + tonic_build::compile_protos("proto/wellknown.proto").unwrap(); } diff --git a/tonic-build/README.md b/tonic-build/README.md index 6a272ae..c7a5652 100644 --- a/tonic-build/README.md +++ b/tonic-build/README.md @@ -23,7 +23,7 @@ tonic-build = ```rust fn main() -> Result<(), Box> { - tonic_build::prost::compile_protos("proto/service.proto")?; + tonic_build::compile_protos("proto/service.proto")?; Ok(()) } ``` @@ -32,7 +32,7 @@ fn main() -> Result<(), Box> { ```rust fn main() -> Result<(), Box> { - tonic_build::prost::configure() + tonic_build::configure() .build_server(false) .compile( &["proto/helloworld/helloworld.proto"], diff --git a/tonic-build/src/client.rs b/tonic-build/src/client.rs index 39b8c04..bd69b98 100644 --- a/tonic-build/src/client.rs +++ b/tonic-build/src/client.rs @@ -1,13 +1,13 @@ -use super::schema::{Context, Method, Service}; +use super::schema::{Method, Service}; use crate::{generate_doc_comments, naive_snake_case}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; /// Generate service for client -pub fn generate<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream { +pub fn generate(service: &T, proto_path: &str) -> TokenStream { let service_ident = quote::format_ident!("{}Client", service.name()); let client_mod = quote::format_ident!("{}_client", naive_snake_case(&service.name())); - let methods = generate_methods(service, context); + let methods = generate_methods(service, proto_path); let connect = generate_connect(&service_ident); let service_doc = generate_doc_comments(service.comment()); @@ -76,7 +76,7 @@ fn generate_connect(_service_ident: &syn::Ident) -> TokenStream { TokenStream::new() } -fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream { +fn generate_methods(service: &T, proto_path: &str) -> TokenStream { let mut stream = TokenStream::new(); for method in service.methods() { @@ -92,10 +92,10 @@ fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> stream.extend(generate_doc_comments(method.comment())); let method = match (method.client_streaming(), method.server_streaming()) { - (false, false) => generate_unary(method, &context, path), - (false, true) => generate_server_streaming(method, &context, path), - (true, false) => generate_client_streaming(method, &context, path), - (true, true) => generate_streaming(method, &context, path), + (false, false) => generate_unary(method, proto_path, path), + (false, true) => generate_server_streaming(method, proto_path, path), + (true, false) => generate_client_streaming(method, proto_path, path), + (true, true) => generate_streaming(method, proto_path, path), }; stream.extend(method); @@ -104,14 +104,10 @@ fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> stream } -fn generate_unary<'a, T: Method<'a>>( - method: &T, - context: &T::Context, - path: String, -) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); +fn generate_unary(method: &T, proto_path: &str, path: String) -> TokenStream { + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let ident = format_ident!("{}", method.name()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); quote! { pub async fn #ident( @@ -128,15 +124,11 @@ fn generate_unary<'a, T: Method<'a>>( } } -fn generate_server_streaming<'a, T: Method<'a>>( - method: &T, - context: &T::Context, - path: String, -) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); +fn generate_server_streaming(method: &T, proto_path: &str, path: String) -> TokenStream { + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let ident = format_ident!("{}", method.name()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); quote! { pub async fn #ident( @@ -153,15 +145,11 @@ fn generate_server_streaming<'a, T: Method<'a>>( } } -fn generate_client_streaming<'a, T: Method<'a>>( - method: &T, - context: &T::Context, - path: String, -) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); +fn generate_client_streaming(method: &T, proto_path: &str, path: String) -> TokenStream { + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let ident = format_ident!("{}", method.name()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); quote! { pub async fn #ident( @@ -178,15 +166,11 @@ fn generate_client_streaming<'a, T: Method<'a>>( } } -fn generate_streaming<'a, T: Method<'a>>( - method: &T, - context: &T::Context, - path: String, -) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); +fn generate_streaming(method: &T, proto_path: &str, path: String) -> TokenStream { + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let ident = format_ident!("{}", method.name()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); quote! { pub async fn #ident( diff --git a/tonic-build/src/lib.rs b/tonic-build/src/lib.rs index bd714cc..cc7bf23 100644 --- a/tonic-build/src/lib.rs +++ b/tonic-build/src/lib.rs @@ -23,7 +23,7 @@ //! //! ```rust,no_run //! fn main() -> Result<(), Box> { -//! tonic_build::prost::compile_protos("proto/service.proto")?; +//! tonic_build::compile_protos("proto/service.proto")?; //! Ok(()) //! } //! ``` @@ -32,7 +32,7 @@ //! //! ```rust,no_run //! fn main() -> Result<(), Box> { -//! tonic_build::prost::configure() +//! tonic_build::configure() //! .build_server(false) //! .compile( //! &["proto/helloworld/helloworld.proto"], @@ -61,7 +61,11 @@ use quote::TokenStreamExt; /// Prost generator #[cfg(feature = "prost")] -pub mod prost; +mod prost; + +#[cfg(feature = "prost")] +pub use prost::{compile_protos, configure, Builder}; + /// Traits to describe schema pub mod schema; diff --git a/tonic-build/src/prost.rs b/tonic-build/src/prost.rs index 924abf0..6264350 100644 --- a/tonic-build/src/prost.rs +++ b/tonic-build/src/prost.rs @@ -5,32 +5,20 @@ use quote::ToTokens; use std::io; use std::path::{Path, PathBuf}; -impl<'a> schema::Commentable<'a> for Service { +const PROST_CODEC_PATH: &'static str = "tonic::codec::ProstCodec"; + +impl schema::Commentable for Service { type Comment = String; - type CommentContainer = &'a Vec; - fn comment(&'a self) -> Self::CommentContainer { - &self.comments.leading + fn comment(&self) -> &[Self::Comment] { + &self.comments.leading[..] } } -/// Context data used while generate prost service -#[derive(Debug)] -pub struct ProstContext { - /// relative path to proto definitions from service definitions - pub proto_path: String, -} +impl schema::Service for Service { + const CODEC_PATH: &'static str = PROST_CODEC_PATH; -impl schema::Context for ProstContext { - fn codec_name(&self) -> &str { - "tonic::codec::ProstCodec" - } -} - -impl<'a> schema::Service<'a> for Service { type Method = Method; - type MethodContainer = &'a Vec; - type Context = ProstContext; fn name(&self) -> &str { &self.name @@ -44,22 +32,21 @@ impl<'a> schema::Service<'a> for Service { &self.proto_name } - fn methods(&'a self) -> Self::MethodContainer { - &self.methods + fn methods(&self) -> &[Self::Method] { + &self.methods[..] } } -impl<'a> schema::Commentable<'a> for Method { +impl schema::Commentable for Method { type Comment = String; - type CommentContainer = &'a Vec; - fn comment(&'a self) -> Self::CommentContainer { - &self.comments.leading + fn comment(&self) -> &[Self::Comment] { + &self.comments.leading[..] } } -impl<'a> schema::Method<'a> for Method { - type Context = ProstContext; +impl schema::Method for Method { + const CODEC_PATH: &'static str = PROST_CODEC_PATH; fn name(&self) -> &str { &self.name @@ -77,13 +64,13 @@ impl<'a> schema::Method<'a> for Method { self.server_streaming } - fn request_response_name(&self, context: &Self::Context) -> (TokenStream, TokenStream) { + fn request_response_name(&self, proto_path: &str) -> (TokenStream, TokenStream) { let request = if self.input_proto_type.starts_with(".google.protobuf") || self.input_type.starts_with("::") { self.input_type.parse::().unwrap() } else { - syn::parse_str::(&format!("{}::{}", context.proto_path, self.input_type)) + syn::parse_str::(&format!("{}::{}", proto_path, self.input_type)) .unwrap() .to_token_stream() }; @@ -93,7 +80,7 @@ impl<'a> schema::Method<'a> for Method { { self.output_type.parse::().unwrap() } else { - syn::parse_str::(&format!("{}::{}", context.proto_path, self.output_type)) + syn::parse_str::(&format!("{}::{}", proto_path, self.output_type)) .unwrap() .to_token_stream() }; @@ -145,17 +132,13 @@ impl ServiceGenerator { impl prost_build::ServiceGenerator for ServiceGenerator { fn generate(&mut self, service: prost_build::Service, _buf: &mut String) { - let context = ProstContext { - proto_path: String::from("super"), - }; - if self.builder.build_server { - let server = server::generate(&service, &context); + let server = server::generate(&service, &self.builder.proto_path); self.servers.extend(server); } if self.builder.build_client { - let client = client::generate(&service, &context); + let client = client::generate(&service, &self.builder.proto_path); self.clients.extend(client); } } @@ -197,6 +180,7 @@ pub struct Builder { pub(crate) extern_path: Vec<(String, String)>, pub(crate) field_attributes: Vec<(String, String)>, pub(crate) type_attributes: Vec<(String, String)>, + pub(crate) proto_path: String, out_dir: Option, #[cfg(feature = "rustfmt")] @@ -262,6 +246,15 @@ impl Builder { self } + /// Set the path to where tonic will search for the Request/Response proto structs + /// live relative to the module where you call `include_proto!`. + /// + /// This defaults to `super` since tonic will generate code in a module. + pub fn proto_path(mut self, proto_path: impl AsRef) -> Self { + self.proto_path = proto_path.as_ref().to_string(); + self + } + /// Compile the .proto files and execute code generation. pub fn compile>(self, protos: &[P], includes: &[P]) -> io::Result<()> { let out_dir = if let Some(out_dir) = self.out_dir.as_ref() { @@ -297,6 +290,7 @@ pub fn configure() -> Builder { extern_path: Vec::new(), field_attributes: Vec::new(), type_attributes: Vec::new(), + proto_path: "super".to_string(), #[cfg(feature = "rustfmt")] format: true, } @@ -306,8 +300,8 @@ pub fn configure() -> Builder { /// /// 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) -> io::Result<()> { - let proto_path: &Path = proto_path.as_ref(); +pub fn compile_protos(proto: impl AsRef) -> io::Result<()> { + let proto_path: &Path = proto.as_ref(); // directory the main .proto file resides in let proto_dir = proto_path diff --git a/tonic-build/src/schema.rs b/tonic-build/src/schema.rs index 7bafa68..04fc108 100644 --- a/tonic-build/src/schema.rs +++ b/tonic-build/src/schema.rs @@ -1,29 +1,20 @@ use proc_macro2::TokenStream; -/// Context data used in code generation -pub trait Context { - /// Provide name of tonic compatibale codec - fn codec_name(&self) -> &str; -} - /// Item has comment -pub trait Commentable<'a> { +pub trait Commentable { /// Comment type - type Comment: AsRef + 'a; - /// Container has comments. - type CommentContainer: IntoIterator; + type Comment: AsRef; /// Get comments about this item - fn comment(&'a self) -> Self::CommentContainer; + fn comment(&self) -> &[Self::Comment]; } /// Service -pub trait Service<'a>: Commentable<'a> { +pub trait Service: Commentable { + /// Path to the codec + const CODEC_PATH: &'static str; + /// Method type - type Method: Method<'a, Context = Self::Context> + 'a; - /// Container has methods - type MethodContainer: IntoIterator; - /// Common context - type Context: Context + 'a; + type Method: Method; /// Name of service fn name(&self) -> &str; @@ -32,13 +23,13 @@ pub trait Service<'a>: Commentable<'a> { /// Identifier used to generate type name fn identifier(&self) -> &str; /// Methods provided by service - fn methods(&'a self) -> Self::MethodContainer; + fn methods(&self) -> &[Self::Method]; } /// Method -pub trait Method<'a>: Commentable<'a> { - /// Common context - type Context: Context + 'a; +pub trait Method: Commentable { + /// Path to the codec + const CODEC_PATH: &'static str; /// Name of method fn name(&self) -> &str; @@ -49,5 +40,5 @@ pub trait Method<'a>: Commentable<'a> { /// Method is streamed by server fn server_streaming(&self) -> bool; /// Type name of request and response - fn request_response_name(&self, context: &Self::Context) -> (TokenStream, TokenStream); + fn request_response_name(&self, proto_path: &str) -> (TokenStream, TokenStream); } diff --git a/tonic-build/src/server.rs b/tonic-build/src/server.rs index b9e687b..2f80486 100644 --- a/tonic-build/src/server.rs +++ b/tonic-build/src/server.rs @@ -1,17 +1,17 @@ -use super::schema::{Commentable, Context, Method, Service}; +use super::schema::{Commentable, Method, Service}; use crate::{generate_doc_comment, generate_doc_comments, naive_snake_case}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Lit, LitStr}; /// Generate service for Server -pub fn generate<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream { - let methods = generate_methods(service, context); +pub fn generate(service: &T, proto_path: &str) -> TokenStream { + let methods = generate_methods(service, proto_path); let server_service = quote::format_ident!("{}Server", service.name()); let server_trait = quote::format_ident!("{}", service.name()); let server_mod = quote::format_ident!("{}_server", naive_snake_case(&service.name())); - let generated_trait = generate_trait(service, context, server_trait.clone()); + let generated_trait = generate_trait(service, proto_path, server_trait.clone()); let service_doc = generate_doc_comments(service.comment()); // Transport based implementations @@ -104,12 +104,8 @@ pub fn generate<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> Tok } } -fn generate_trait<'a, T: Service<'a>>( - service: &'a T, - context: &T::Context, - server_trait: Ident, -) -> TokenStream { - let methods = generate_trait_methods(service, context); +fn generate_trait(service: &T, proto_path: &str, server_trait: Ident) -> TokenStream { + let methods = generate_trait_methods(service, proto_path); let trait_doc = generate_doc_comment(&format!( "Generated trait containing gRPC methods that should be implemented for use with {}Server.", service.name() @@ -124,13 +120,13 @@ fn generate_trait<'a, T: Service<'a>>( } } -fn generate_trait_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream { +fn generate_trait_methods(service: &T, proto_path: &str) -> TokenStream { let mut stream = TokenStream::new(); for method in service.methods() { let name = quote::format_ident!("{}", method.name()); - let (req_message, res_message) = method.request_response_name(context); + let (req_message, res_message) = method.request_response_name(proto_path); let method_doc = generate_doc_comments(method.comment()); @@ -213,7 +209,7 @@ fn generate_transport( TokenStream::new() } -fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream { +fn generate_methods(service: &T, proto_path: &str) -> TokenStream { let mut stream = TokenStream::new(); for method in service.methods() { @@ -228,16 +224,16 @@ fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> let server_trait = quote::format_ident!("{}", service.name()); let method_stream = match (method.client_streaming(), method.server_streaming()) { - (false, false) => generate_unary(method, ident, context, server_trait), + (false, false) => generate_unary(method, proto_path, ident, server_trait), (false, true) => { - generate_server_streaming(method, ident.clone(), context, server_trait) + generate_server_streaming(method, proto_path, ident.clone(), server_trait) } (true, false) => { - generate_client_streaming(method, ident.clone(), context, server_trait) + generate_client_streaming(method, proto_path, ident.clone(), server_trait) } - (true, true) => generate_streaming(method, ident.clone(), context, server_trait), + (true, true) => generate_streaming(method, proto_path, ident.clone(), server_trait), }; let method = quote! { @@ -251,17 +247,17 @@ fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> stream } -fn generate_unary<'a, T: Method<'a>>( +fn generate_unary( method: &T, + proto_path: &str, method_ident: Ident, - context: &T::Context, server_trait: Ident, ) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let service_ident = quote::format_ident!("{}Svc", method.identifier()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); quote! { #[allow(non_camel_case_types)] @@ -301,17 +297,17 @@ fn generate_unary<'a, T: Method<'a>>( } } -fn generate_server_streaming<'a, T: Method<'a>>( +fn generate_server_streaming( method: &T, + proto_path: &str, method_ident: Ident, - context: &T::Context, server_trait: Ident, ) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let service_ident = quote::format_ident!("{}Svc", method.identifier()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); let response_stream = quote::format_ident!("{}Stream", method.identifier()); @@ -355,16 +351,16 @@ fn generate_server_streaming<'a, T: Method<'a>>( } } -fn generate_client_streaming<'a, T: Method<'a>>( +fn generate_client_streaming( method: &T, + proto_path: &str, method_ident: Ident, - context: &T::Context, server_trait: Ident, ) -> TokenStream { let service_ident = quote::format_ident!("{}Svc", method.identifier()); - let (request, response) = method.request_response_name(context); - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); + let (request, response) = method.request_response_name(proto_path); + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); quote! { #[allow(non_camel_case_types)] @@ -406,17 +402,17 @@ fn generate_client_streaming<'a, T: Method<'a>>( } } -fn generate_streaming<'a, T: Method<'a>>( +fn generate_streaming( method: &T, + proto_path: &str, method_ident: Ident, - context: &T::Context, server_trait: Ident, ) -> TokenStream { - let codec_name = syn::parse_str::(context.codec_name()).unwrap(); + let codec_name = syn::parse_str::(T::CODEC_PATH).unwrap(); let service_ident = quote::format_ident!("{}Svc", method.identifier()); - let (request, response) = method.request_response_name(context); + let (request, response) = method.request_response_name(proto_path); let response_stream = quote::format_ident!("{}Stream", method.identifier());