1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::{
    ffi::OsString,
    path::{Path, PathBuf},
};

/// A mirror of [`tonic_build::Builder`] for our own control
pub struct Builder {
    pub(crate) tonic: tonic_build::Builder,
    pub(crate) prost: prost_build::Config,
    pub(crate) protoc_args: Vec<OsString>,
    pub(crate) out_dir: Option<PathBuf>,
    pub(crate) force: bool,
    pub(crate) default_module_name: Option<String>,
    pub(crate) follow_links: bool,
    pub(crate) file_descriptor_set_path: Option<PathBuf>,
}

impl Default for Builder {
    fn default() -> Self {
        Self {
            tonic: tonic_build::configure(),
            prost: Default::default(),
            protoc_args: Default::default(),
            out_dir: None,
            force: false,
            default_module_name: None,
            follow_links: false,
            file_descriptor_set_path: None,
        }
    }
}

impl Builder {
    pub(crate) fn get_out_dir(&self) -> Result<PathBuf, anyhow::Error> {
        self.out_dir.clone().map(Ok).unwrap_or_else(|| {
            std::env::var_os("OUT_DIR")
                .ok_or_else(|| anyhow::anyhow!("could not determine $OUT_DIR"))
                .map(Into::into)
        })
    }

    pub fn new() -> Self {
        Default::default()
    }

    pub fn force(mut self, force: bool) -> Self {
        self.force = force;
        self
    }

    /// Follow symbolic links when finding .proto files.
    ///
    /// This defaults to `false`.
    pub fn follow_links(mut self, follow_links: bool) -> Self {
        self.follow_links = follow_links;
        self
    }

    pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
        self.out_dir = Some(out_dir.as_ref().to_owned());
        self
    }

    /// Configures what filename protobufs with no package definition are written to.
    pub fn default_module_name(mut self, name: impl AsRef<str>) -> Self {
        self.default_module_name = Some(name.as_ref().to_string());
        self
    }

    /// Enable or disable gRPC client code generation.
    pub fn build_client(mut self, enable: bool) -> Self {
        self.tonic = self.tonic.build_client(enable);
        self
    }

    /// Enable or disable gRPC server code generation.
    pub fn build_server(mut self, enable: bool) -> Self {
        self.tonic = self.tonic.build_server(enable);
        self
    }

    /// Declare externally provided Protobuf package or type.
    ///
    /// Passed directly to `prost_build::Config.extern_path`.
    /// Note that both the Protobuf path and the rust package paths should both be fully qualified.
    /// i.e. Protobuf paths should start with "." and rust paths should start with "::"
    pub fn extern_path(mut self, proto_path: impl AsRef<str>, rust_path: impl AsRef<str>) -> Self {
        self.prost.extern_path(
            proto_path.as_ref().to_string(),
            rust_path.as_ref().to_string(),
        );
        self
    }

    /// Add additional attribute to matched messages, enums, and one-offs.
    ///
    /// Passed directly to `prost_build::Config.field_attribute`.
    pub fn field_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
        self.prost
            .field_attribute(path.as_ref(), attribute.as_ref());
        self
    }

    /// Add additional attribute to matched messages, enums, and one-offs.
    ///
    /// Passed directly to `prost_build::Config.type_attribute`.
    pub fn type_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
        self.prost.type_attribute(path.as_ref(), attribute.as_ref());
        self
    }

    /// Add additional attribute to matched server `mod`s. Matches on the package name.
    pub fn server_mod_attribute<P: AsRef<str>, A: AsRef<str>>(
        mut self,
        path: P,
        attribute: A,
    ) -> Self {
        self.tonic = self.tonic.server_mod_attribute(path, attribute);
        self
    }

    /// Add additional attribute to matched service servers. Matches on the service name.
    pub fn server_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
        self.tonic = self.tonic.server_attribute(path, attribute);
        self
    }

    /// Add additional attribute to matched client `mod`s. Matches on the package name.
    pub fn client_mod_attribute<P: AsRef<str>, A: AsRef<str>>(
        mut self,
        path: P,
        attribute: A,
    ) -> Self {
        self.tonic = self.tonic.client_mod_attribute(path, attribute);
        self
    }

    /// Add additional attribute to matched service clients. Matches on the service name.
    pub fn client_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
        self.tonic = self.tonic.client_attribute(path, attribute);
        self
    }

    /// Configure Prost `protoc_args` build arguments.
    ///
    /// Note: Enabling `--experimental_allow_proto3_optional` requires protobuf >= 3.12.
    pub fn protoc_arg<A: AsRef<str>>(mut self, arg: A) -> Self {
        self.protoc_args.push(arg.as_ref().into());
        self
    }

    /// Enable or disable directing Prost to compile well-known protobuf types instead
    /// of using the already-compiled versions available in the `prost-types` crate.
    ///
    /// This defaults to `false`.
    pub fn compile_well_known_types(mut self, compile_well_known_types: bool) -> Self {
        if compile_well_known_types {
            self.prost.compile_well_known_types();
        };
        self
    }

    /// Configures the optional module filename for easy inclusion of all generated Rust files
    ///
    /// If set, generates a file (inside the `OUT_DIR` or `out_dir()` as appropriate) which contains
    /// a set of `pub mod XXX` statements combining to load all Rust files generated.  This can allow
    /// for a shortcut where multiple related proto files have been compiled together resulting in
    /// a semi-complex set of includes.
    pub fn include_file(mut self, path: impl AsRef<Path>) -> Self {
        self.prost.include_file(path.as_ref());
        self
    }

    /// When set, the `FileDescriptorSet` generated by protoc is written to the provided filesystem path.
    ///
    /// This option can be used in conjunction with the `include_bytes!` macro and the types in the `prost-types` crate
    /// for implementing reflection capabilities, among other things.
    pub fn file_descriptor_set_path(mut self, path: impl Into<PathBuf>) -> Self {
        self.file_descriptor_set_path = Some(path.into());
        self
    }
}