xref: /tonic/tonic-build/src/lib.rs (revision 02487bec)
1 //! `tonic-build` compiles `proto` files via `prost` and generates service stubs
2 //! and proto definitiones for use with `tonic`.
3 //!
4 //! # Examples
5 //! Simple
6 //!
7 //! ```rust,no_run
8 //! fn main() -> Result<(), Box<dyn std::error::Error>> {
9 //!     tonic_build::compile_protos("proto/service.proto")?;
10 //!     Ok(())
11 //! }
12 //! ```
13 //!
14 //! Configuration
15 //!
16 //! ```rust,no_run
17 //! fn main() -> Result<(), Box<dyn std::error::Error>> {
18 //!    tonic_build::configure()
19 //!         .build_server(false)
20 //!         .compile(
21 //!             &["proto/helloworld/helloworld.proto"],
22 //!             &["proto/helloworld"],
23 //!         )?;
24 //!    Ok(())
25 //! }
26 //! ```
27 
28 use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
29 use prost_build::Config;
30 use quote::TokenStreamExt;
31 
32 #[cfg(feature = "rustfmt")]
33 use std::process::Command;
34 use std::{
35     io,
36     path::{Path, PathBuf},
37 };
38 
39 mod client;
40 mod service;
41 
42 #[derive(Clone)]
43 pub struct Builder {
44     build_client: bool,
45     build_server: bool,
46     out_dir: Option<PathBuf>,
47 }
48 
49 impl Builder {
50     /// Enable or disable gRPC client code generation.
51     pub fn build_client(mut self, enable: bool) -> Self {
52         self.build_client = enable;
53         self
54     }
55 
56     /// Enable or disable gRPC server code generation.
57     pub fn build_server(mut self, enable: bool) -> Self {
58         self.build_server = enable;
59         self
60     }
61 
62     /// Set the output directory to generate code to.
63     ///
64     /// Defaults to the `OUT_DIR` environment variable.
65     pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
66         self.out_dir = Some(out_dir.as_ref().to_path_buf());
67         self
68     }
69 
70     /// Compile the .proto files and execute code generation.
71     pub fn compile<P: AsRef<Path>>(self, protos: &[P], includes: &[P]) -> io::Result<()> {
72         let mut config = Config::new();
73 
74         let out_dir = self
75             .out_dir
76             .clone()
77             .unwrap_or_else(|| PathBuf::from(std::env::var("OUT_DIR").unwrap()));
78 
79         config.out_dir(out_dir.clone());
80         config.service_generator(Box::new(ServiceGenerator::new(self)));
81         config.compile_protos(protos, includes)?;
82 
83         #[cfg(feature = "rustfmt")]
84         fmt(out_dir.to_str().expect("Expected utf8 out_dir"));
85 
86         Ok(())
87     }
88 }
89 
90 /// Configure tonic-build code generation.
91 ///
92 /// Use [`compile_protos`] instead if you don't need to tweak anything.
93 pub fn configure() -> Builder {
94     Builder {
95         build_client: true,
96         build_server: true,
97         out_dir: None,
98     }
99 }
100 
101 /// Simple `.proto` compiling. Use [`configure`] instead if you need more options.
102 ///
103 /// The include directory will be the parent folder of the specified path.
104 /// The package name will be the filename without the extension.
105 pub fn compile_protos(proto_path: impl AsRef<Path>) -> io::Result<()> {
106     let proto_path: &Path = proto_path.as_ref();
107 
108     // directory the main .proto file resides in
109     let proto_dir = proto_path
110         .parent()
111         .expect("proto file should reside in a directory");
112 
113     self::configure().compile(&[proto_path], &[proto_dir])?;
114 
115     Ok(())
116 }
117 
118 #[cfg(feature = "rustfmt")]
119 fn fmt(out_dir: &str) {
120     let dir = std::fs::read_dir(out_dir).unwrap();
121 
122     for entry in dir {
123         let file = entry.unwrap().file_name().into_string().unwrap();
124         let out = Command::new("rustfmt")
125             .arg("--emit")
126             .arg("files")
127             .arg("--edition")
128             .arg("2018")
129             .arg(format!("{}/{}", out_dir, file))
130             .output()
131             .unwrap();
132 
133         println!("out: {:?}", out);
134         assert!(out.status.success());
135     }
136 }
137 
138 pub struct ServiceGenerator {
139     builder: Builder,
140     clients: TokenStream,
141     servers: TokenStream,
142 }
143 
144 impl ServiceGenerator {
145     fn new(builder: Builder) -> Self {
146         ServiceGenerator {
147             builder,
148             clients: TokenStream::default(),
149             servers: TokenStream::default(),
150         }
151     }
152 }
153 
154 impl prost_build::ServiceGenerator for ServiceGenerator {
155     fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
156         let path = "super";
157 
158         if self.builder.build_server {
159             let server = service::generate(&service, path);
160             self.servers.extend(server);
161         }
162 
163         if self.builder.build_client {
164             let client = client::generate(&service, path);
165             self.clients.extend(client);
166         }
167     }
168 
169     fn finalize(&mut self, buf: &mut String) {
170         if self.builder.build_client && !self.clients.is_empty() {
171             let clients = &self.clients;
172 
173             let client_service = quote::quote! {
174                 /// Generated client implementations.
175                 pub mod client {
176                     #![allow(unused_variables, dead_code, missing_docs)]
177                     use tonic::codegen::*;
178 
179                     #clients
180                 }
181             };
182 
183             let code = format!("{}", client_service);
184             buf.push_str(&code);
185         }
186 
187         if self.builder.build_server && !self.servers.is_empty() {
188             let servers = &self.servers;
189 
190             let server_service = quote::quote! {
191                 /// Generated server implementations.
192                 pub mod server {
193                     #![allow(unused_variables, dead_code, missing_docs)]
194                     use tonic::codegen::*;
195 
196                     #servers
197                 }
198             };
199 
200             let code = format!("{}", server_service);
201             buf.push_str(&code);
202         }
203     }
204 }
205 
206 // Generate a singular line of a doc comment
207 fn generate_doc_comment(comment: &str) -> TokenStream {
208     let mut doc_stream = TokenStream::new();
209 
210     doc_stream.append(Ident::new("doc", Span::call_site()));
211     doc_stream.append(Punct::new('=', Spacing::Alone));
212     doc_stream.append(Literal::string(&comment));
213 
214     let group = Group::new(Delimiter::Bracket, doc_stream);
215 
216     let mut stream = TokenStream::new();
217     stream.append(Punct::new('#', Spacing::Alone));
218     stream.append(group);
219     stream
220 }
221 
222 // Generate a larger doc comment composed of many lines of doc comments
223 fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
224     let mut stream = TokenStream::new();
225 
226     for comment in comments {
227         stream.extend(generate_doc_comment(comment.as_ref()));
228     }
229 
230     stream
231 }
232