Skip to main content

camber_build/
builder.rs

1use crate::codegen;
2use std::io;
3use std::path::{Path, PathBuf};
4
5/// Compile `.proto` files and generate async service traits.
6///
7/// Runs `tonic-build` to produce standard async gRPC server code, then
8/// generates an additional async trait and bridge struct per service.
9/// The trait has async methods. The bridge delegates directly via `.await`.
10///
11/// Call this from your crate's `build.rs`.
12pub fn compile_protos(
13    protos: &[impl AsRef<Path>],
14    includes: &[impl AsRef<Path>],
15) -> io::Result<()> {
16    configure().compile_protos(protos, includes)
17}
18
19/// Return a builder for customizing proto compilation.
20pub fn configure() -> Builder {
21    Builder {
22        file_descriptor_set_path: None,
23    }
24}
25
26/// Builder for customizing `.proto` compilation.
27pub struct Builder {
28    file_descriptor_set_path: Option<PathBuf>,
29}
30
31impl Builder {
32    /// Generate a file containing the encoded `FileDescriptorSet`.
33    /// Required for gRPC reflection.
34    pub fn file_descriptor_set_path(mut self, path: impl AsRef<Path>) -> Self {
35        self.file_descriptor_set_path = Some(path.as_ref().to_path_buf());
36        self
37    }
38
39    /// Compile the `.proto` files and generate code.
40    pub fn compile_protos(
41        self,
42        protos: &[impl AsRef<Path>],
43        includes: &[impl AsRef<Path>],
44    ) -> io::Result<()> {
45        let tonic = tonic_prost_build::configure()
46            .build_client(true)
47            .build_server(true)
48            .build_transport(false);
49
50        let mut config = prost_build::Config::new();
51        configure_protoc(&mut config)?;
52        if let Some(path) = &self.file_descriptor_set_path {
53            config.file_descriptor_set_path(path);
54        }
55        config.service_generator(Box::new(codegen::ServiceGenerator::new(tonic)));
56        let include_paths = include_paths(includes)?;
57        config.compile_protos(protos, &include_paths)
58    }
59}
60
61fn configure_protoc(config: &mut prost_build::Config) -> io::Result<()> {
62    let protoc = protoc_bin_vendored::protoc_bin_path().map_err(other_io_error)?;
63    config.protoc_executable(protoc);
64    Ok(())
65}
66
67fn include_paths(includes: &[impl AsRef<Path>]) -> io::Result<Vec<PathBuf>> {
68    let mut include_paths = includes
69        .iter()
70        .map(|path| path.as_ref().to_path_buf())
71        .collect::<Vec<_>>();
72    let vendored_include = protoc_bin_vendored::include_path().map_err(other_io_error)?;
73    if !include_paths.iter().any(|path| path == &vendored_include) {
74        include_paths.push(vendored_include);
75    }
76    Ok(include_paths)
77}
78
79fn other_io_error(error: impl std::fmt::Display) -> io::Error {
80    io::Error::other(error.to_string())
81}