* generate rustdoc comments from .proto file comments * move imports to top of file * more docs gen * trait docs * more docs * use singular doc gen fn here * remove pesky space
232 lines
6.2 KiB
Rust
232 lines
6.2 KiB
Rust
//! `tonic-build` compiles `proto` files via `prost` and generates service stubs
|
|
//! and proto definitiones for use with `tonic`.
|
|
//!
|
|
//! # Examples
|
|
//! Simple
|
|
//!
|
|
//! ```rust,no_run
|
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
//! tonic_build::compile_protos("proto/service.proto")?;
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Configuration
|
|
//!
|
|
//! ```rust,no_run
|
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
//! tonic_build::configure()
|
|
//! .build_server(false)
|
|
//! .compile(
|
|
//! &["proto/helloworld/helloworld.proto"],
|
|
//! &["proto/helloworld"],
|
|
//! )?;
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
|
|
use prost_build::Config;
|
|
use quote::TokenStreamExt;
|
|
|
|
#[cfg(feature = "rustfmt")]
|
|
use std::process::Command;
|
|
use std::{
|
|
io,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
mod client;
|
|
mod service;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Builder {
|
|
build_client: bool,
|
|
build_server: bool,
|
|
out_dir: Option<PathBuf>,
|
|
}
|
|
|
|
impl Builder {
|
|
/// Enable or disable gRPC client code generation.
|
|
pub fn build_client(mut self, enable: bool) -> Self {
|
|
self.build_client = enable;
|
|
self
|
|
}
|
|
|
|
/// Enable or disable gRPC server code generation.
|
|
pub fn build_server(mut self, enable: bool) -> Self {
|
|
self.build_server = enable;
|
|
self
|
|
}
|
|
|
|
/// Set the output directory to generate code to.
|
|
///
|
|
/// Defaults to the `OUT_DIR` environment variable.
|
|
pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
|
|
self.out_dir = Some(out_dir.as_ref().to_path_buf());
|
|
self
|
|
}
|
|
|
|
/// Compile the .proto files and execute code generation.
|
|
pub fn compile<P: AsRef<Path>>(self, protos: &[P], includes: &[P]) -> io::Result<()> {
|
|
let mut config = Config::new();
|
|
|
|
let out_dir = self
|
|
.out_dir
|
|
.clone()
|
|
.unwrap_or_else(|| PathBuf::from(std::env::var("OUT_DIR").unwrap()));
|
|
|
|
config.out_dir(out_dir.clone());
|
|
config.service_generator(Box::new(ServiceGenerator::new(self)));
|
|
config.compile_protos(protos, includes)?;
|
|
|
|
#[cfg(feature = "rustfmt")]
|
|
fmt(out_dir.to_str().expect("Expected utf8 out_dir"));
|
|
|
|
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: None,
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
|
|
// 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])?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "rustfmt")]
|
|
fn fmt(out_dir: &str) {
|
|
let dir = std::fs::read_dir(out_dir).unwrap();
|
|
|
|
for entry in dir {
|
|
let file = entry.unwrap().file_name().into_string().unwrap();
|
|
let out = Command::new("rustfmt")
|
|
.arg("--emit")
|
|
.arg("files")
|
|
.arg("--edition")
|
|
.arg("2018")
|
|
.arg(format!("{}/{}", out_dir, file))
|
|
.output()
|
|
.unwrap();
|
|
|
|
println!("out: {:?}", out);
|
|
assert!(out.status.success());
|
|
}
|
|
}
|
|
|
|
pub struct ServiceGenerator {
|
|
builder: Builder,
|
|
clients: TokenStream,
|
|
servers: TokenStream,
|
|
}
|
|
|
|
impl ServiceGenerator {
|
|
fn new(builder: Builder) -> Self {
|
|
ServiceGenerator {
|
|
builder,
|
|
clients: TokenStream::default(),
|
|
servers: TokenStream::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl prost_build::ServiceGenerator for ServiceGenerator {
|
|
fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
|
|
let path = "super";
|
|
|
|
if self.builder.build_server {
|
|
let server = service::generate(&service, path);
|
|
self.servers.extend(server);
|
|
}
|
|
|
|
if self.builder.build_client {
|
|
let client = client::generate(&service, path);
|
|
self.clients.extend(client);
|
|
}
|
|
}
|
|
|
|
fn finalize(&mut self, buf: &mut String) {
|
|
if self.builder.build_client && !self.clients.is_empty() {
|
|
let clients = &self.clients;
|
|
|
|
let client_service = quote::quote! {
|
|
/// Generated client implementations.
|
|
pub mod client {
|
|
#![allow(unused_variables, dead_code, missing_docs)]
|
|
use tonic::codegen::*;
|
|
|
|
#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! {
|
|
/// Generated server implementations.
|
|
pub mod server {
|
|
#![allow(unused_variables, dead_code, missing_docs)]
|
|
use tonic::codegen::*;
|
|
|
|
#servers
|
|
}
|
|
};
|
|
|
|
let code = format!("{}", server_service);
|
|
buf.push_str(&code);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate a singular line of a doc comment
|
|
fn generate_doc_comment(comment: &str) -> TokenStream {
|
|
let mut doc_stream = TokenStream::new();
|
|
|
|
doc_stream.append(Ident::new("doc", Span::call_site()));
|
|
doc_stream.append(Punct::new('=', Spacing::Alone));
|
|
doc_stream.append(Literal::string(&comment));
|
|
|
|
let group = Group::new(Delimiter::Bracket, doc_stream);
|
|
|
|
let mut stream = TokenStream::new();
|
|
stream.append(Punct::new('#', Spacing::Alone));
|
|
stream.append(group);
|
|
stream
|
|
}
|
|
|
|
// Generate a larger doc comment composed of many lines of doc comments
|
|
fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
|
|
let mut stream = TokenStream::new();
|
|
|
|
for comment in comments {
|
|
stream.extend(generate_doc_comment(comment.as_ref()));
|
|
}
|
|
|
|
stream
|
|
}
|