prost_build/config.rs
1use std::collections::HashMap;
2use std::default;
3use std::env;
4use std::ffi::{OsStr, OsString};
5use std::fmt;
6use std::fs;
7use std::io::{Error, ErrorKind, Result, Write};
8use std::path::{Path, PathBuf};
9use std::process::Command;
10
11use log::debug;
12use log::trace;
13
14use prost::Message;
15use prost_types::{FileDescriptorProto, FileDescriptorSet};
16
17use crate::code_generator::CodeGenerator;
18use crate::context::Context;
19use crate::extern_paths::ExternPaths;
20use crate::message_graph::MessageGraph;
21use crate::path::PathMap;
22use crate::BytesType;
23use crate::MapType;
24use crate::Module;
25use crate::ServiceGenerator;
26
27/// Configuration options for Protobuf code generation.
28///
29/// This configuration builder can be used to set non-default code generation options.
30pub struct Config {
31 pub(crate) file_descriptor_set_path: Option<PathBuf>,
32 pub(crate) service_generator: Option<Box<dyn ServiceGenerator>>,
33 pub(crate) map_type: PathMap<MapType>,
34 pub(crate) bytes_type: PathMap<BytesType>,
35 pub(crate) type_attributes: PathMap<String>,
36 pub(crate) message_attributes: PathMap<String>,
37 pub(crate) enum_attributes: PathMap<String>,
38 pub(crate) field_attributes: PathMap<String>,
39 pub(crate) boxed: PathMap<()>,
40 pub(crate) prost_types: bool,
41 pub(crate) strip_enum_prefix: bool,
42 pub(crate) out_dir: Option<PathBuf>,
43 pub(crate) extern_paths: Vec<(String, String)>,
44 pub(crate) default_package_filename: String,
45 pub(crate) enable_type_names: bool,
46 pub(crate) type_name_domains: PathMap<String>,
47 pub(crate) protoc_args: Vec<OsString>,
48 pub(crate) protoc_executable: PathBuf,
49 pub(crate) disable_comments: PathMap<()>,
50 pub(crate) skip_debug: PathMap<()>,
51 pub(crate) skip_protoc_run: bool,
52 pub(crate) skip_source_info: bool,
53 pub(crate) include_file: Option<PathBuf>,
54 pub(crate) prost_path: Option<String>,
55 pub(crate) prost_types_path: Option<String>,
56 #[cfg(feature = "format")]
57 pub(crate) fmt: bool,
58}
59
60impl Config {
61 /// Creates a new code generator configuration with default options.
62 pub fn new() -> Config {
63 Config::default()
64 }
65
66 /// Configure the code generator to generate Rust [`BTreeMap`][1] fields for Protobuf
67 /// [`map`][2] type fields.
68 ///
69 /// # Arguments
70 ///
71 /// **`paths`** - paths to specific fields, messages, or packages which should use a Rust
72 /// `BTreeMap` for Protobuf `map` fields. Paths are specified in terms of the Protobuf type
73 /// name (not the generated Rust type name). Paths with a leading `.` are treated as fully
74 /// qualified names. Paths without a leading `.` are treated as relative, and are suffix
75 /// matched on the fully qualified field name. If a Protobuf map field matches any of the
76 /// paths, a Rust `BTreeMap` field is generated instead of the default [`HashMap`][3].
77 ///
78 /// The matching is done on the Protobuf names, before converting to Rust-friendly casing
79 /// standards.
80 ///
81 /// # Examples
82 ///
83 /// ```rust
84 /// # let mut config = prost_build::Config::new();
85 /// // Match a specific field in a message type.
86 /// config.btree_map(&[".my_messages.MyMessageType.my_map_field"]);
87 ///
88 /// // Match all map fields in a message type.
89 /// config.btree_map(&[".my_messages.MyMessageType"]);
90 ///
91 /// // Match all map fields in a package.
92 /// config.btree_map(&[".my_messages"]);
93 ///
94 /// // Match all map fields. Specially useful in `no_std` contexts.
95 /// config.btree_map(&["."]);
96 ///
97 /// // Match all map fields in a nested message.
98 /// config.btree_map(&[".my_messages.MyMessageType.MyNestedMessageType"]);
99 ///
100 /// // Match all fields named 'my_map_field'.
101 /// config.btree_map(&["my_map_field"]);
102 ///
103 /// // Match all fields named 'my_map_field' in messages named 'MyMessageType', regardless of
104 /// // package or nesting.
105 /// config.btree_map(&["MyMessageType.my_map_field"]);
106 ///
107 /// // Match all fields named 'my_map_field', and all fields in the 'foo.bar' package.
108 /// config.btree_map(&["my_map_field", ".foo.bar"]);
109 /// ```
110 ///
111 /// [1]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
112 /// [2]: https://protobuf.dev/programming-guides/proto3/#maps
113 /// [3]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
114 pub fn btree_map<I, S>(&mut self, paths: I) -> &mut Self
115 where
116 I: IntoIterator<Item = S>,
117 S: AsRef<str>,
118 {
119 self.map_type.clear();
120 for matcher in paths {
121 self.map_type
122 .insert(matcher.as_ref().to_string(), MapType::BTreeMap);
123 }
124 self
125 }
126
127 /// Configure the code generator to generate Rust [`bytes::Bytes`](prost::bytes::Bytes) fields for Protobuf
128 /// [`bytes`][2] type fields.
129 ///
130 /// # Arguments
131 ///
132 /// **`paths`** - paths to specific fields, messages, or packages which should use a Rust
133 /// `Bytes` for Protobuf `bytes` fields. Paths are specified in terms of the Protobuf type
134 /// name (not the generated Rust type name). Paths with a leading `.` are treated as fully
135 /// qualified names. Paths without a leading `.` are treated as relative, and are suffix
136 /// matched on the fully qualified field name. If a Protobuf map field matches any of the
137 /// paths, a Rust `Bytes` field is generated instead of the default [`Vec<u8>`][3].
138 ///
139 /// The matching is done on the Protobuf names, before converting to Rust-friendly casing
140 /// standards.
141 ///
142 /// # Examples
143 ///
144 /// ```rust
145 /// # let mut config = prost_build::Config::new();
146 /// // Match a specific field in a message type.
147 /// config.bytes(&[".my_messages.MyMessageType.my_bytes_field"]);
148 ///
149 /// // Match all bytes fields in a message type.
150 /// config.bytes(&[".my_messages.MyMessageType"]);
151 ///
152 /// // Match all bytes fields in a package.
153 /// config.bytes(&[".my_messages"]);
154 ///
155 /// // Match all bytes fields. Specially useful in `no_std` contexts.
156 /// config.bytes(&["."]);
157 ///
158 /// // Match all bytes fields in a nested message.
159 /// config.bytes(&[".my_messages.MyMessageType.MyNestedMessageType"]);
160 ///
161 /// // Match all fields named 'my_bytes_field'.
162 /// config.bytes(&["my_bytes_field"]);
163 ///
164 /// // Match all fields named 'my_bytes_field' in messages named 'MyMessageType', regardless of
165 /// // package or nesting.
166 /// config.bytes(&["MyMessageType.my_bytes_field"]);
167 ///
168 /// // Match all fields named 'my_bytes_field', and all fields in the 'foo.bar' package.
169 /// config.bytes(&["my_bytes_field", ".foo.bar"]);
170 /// ```
171 ///
172 /// [2]: https://protobuf.dev/programming-guides/proto3/#scalar
173 /// [3]: https://doc.rust-lang.org/std/vec/struct.Vec.html
174 pub fn bytes<I, S>(&mut self, paths: I) -> &mut Self
175 where
176 I: IntoIterator<Item = S>,
177 S: AsRef<str>,
178 {
179 self.bytes_type.clear();
180 for matcher in paths {
181 self.bytes_type
182 .insert(matcher.as_ref().to_string(), BytesType::Bytes);
183 }
184 self
185 }
186
187 /// Add additional attribute to matched fields.
188 ///
189 /// # Arguments
190 ///
191 /// **`path`** - a path matching any number of fields. These fields get the attribute.
192 /// For details about matching fields see [`btree_map`](Self::btree_map).
193 ///
194 /// **`attribute`** - an arbitrary string that'll be placed before each matched field. The
195 /// expected usage are additional attributes, usually in concert with whole-type
196 /// attributes set with [`type_attribute`](Self::type_attribute), but it is not
197 /// checked and anything can be put there.
198 ///
199 /// Note that the calls to this method are cumulative ‒ if multiple paths from multiple calls
200 /// match the same field, the field gets all the corresponding attributes.
201 ///
202 /// # Examples
203 ///
204 /// ```rust
205 /// # let mut config = prost_build::Config::new();
206 /// // Prost renames fields named `in` to `in_`. But if serialized through serde,
207 /// // they should as `in`.
208 /// config.field_attribute("in", "#[serde(rename = \"in\")]");
209 /// ```
210 pub fn field_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
211 where
212 P: AsRef<str>,
213 A: AsRef<str>,
214 {
215 self.field_attributes
216 .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
217 self
218 }
219
220 /// Add additional attribute to matched messages, enums and one-ofs.
221 ///
222 /// # Arguments
223 ///
224 /// **`paths`** - a path matching any number of types. It works the same way as in
225 /// [`btree_map`](Self::btree_map), just with the field name omitted.
226 ///
227 /// **`attribute`** - an arbitrary string to be placed before each matched type. The
228 /// expected usage are additional attributes, but anything is allowed.
229 ///
230 /// The calls to this method are cumulative. They don't overwrite previous calls and if a
231 /// type is matched by multiple calls of the method, all relevant attributes are added to
232 /// it.
233 ///
234 /// For things like serde it might be needed to combine with [field
235 /// attributes](Self::field_attribute).
236 ///
237 /// # Examples
238 ///
239 /// ```rust
240 /// # let mut config = prost_build::Config::new();
241 /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
242 /// config.type_attribute(".", "#[derive(Eq)]");
243 /// // Some messages want to be serializable with serde as well.
244 /// config.type_attribute("my_messages.MyMessageType",
245 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
246 /// config.type_attribute("my_messages.MyMessageType.MyNestedMessageType",
247 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
248 /// ```
249 ///
250 /// # Oneof fields
251 ///
252 /// The `oneof` fields don't have a type name of their own inside Protobuf. Therefore, the
253 /// field name can be used both with `type_attribute` and `field_attribute` ‒ the first is
254 /// placed before the `enum` type definition, the other before the field inside corresponding
255 /// message `struct`.
256 ///
257 /// In other words, to place an attribute on the `enum` implementing the `oneof`, the match
258 /// would look like `my_messages.MyMessageType.oneofname`.
259 pub fn type_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
260 where
261 P: AsRef<str>,
262 A: AsRef<str>,
263 {
264 self.type_attributes
265 .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
266 self
267 }
268
269 /// Add additional attribute to matched messages.
270 ///
271 /// # Arguments
272 ///
273 /// **`paths`** - a path matching any number of types. It works the same way as in
274 /// [`btree_map`](Self::btree_map), just with the field name omitted.
275 ///
276 /// **`attribute`** - an arbitrary string to be placed before each matched type. The
277 /// expected usage are additional attributes, but anything is allowed.
278 ///
279 /// The calls to this method are cumulative. They don't overwrite previous calls and if a
280 /// type is matched by multiple calls of the method, all relevant attributes are added to
281 /// it.
282 ///
283 /// For things like serde it might be needed to combine with [field
284 /// attributes](Self::field_attribute).
285 ///
286 /// # Examples
287 ///
288 /// ```rust
289 /// # let mut config = prost_build::Config::new();
290 /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
291 /// config.message_attribute(".", "#[derive(Eq)]");
292 /// // Some messages want to be serializable with serde as well.
293 /// config.message_attribute("my_messages.MyMessageType",
294 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
295 /// config.message_attribute("my_messages.MyMessageType.MyNestedMessageType",
296 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
297 /// ```
298 pub fn message_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
299 where
300 P: AsRef<str>,
301 A: AsRef<str>,
302 {
303 self.message_attributes
304 .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
305 self
306 }
307
308 /// Add additional attribute to matched enums and one-ofs.
309 ///
310 /// # Arguments
311 ///
312 /// **`paths`** - a path matching any number of types. It works the same way as in
313 /// [`btree_map`](Self::btree_map), just with the field name omitted.
314 ///
315 /// **`attribute`** - an arbitrary string to be placed before each matched type. The
316 /// expected usage are additional attributes, but anything is allowed.
317 ///
318 /// The calls to this method are cumulative. They don't overwrite previous calls and if a
319 /// type is matched by multiple calls of the method, all relevant attributes are added to
320 /// it.
321 ///
322 /// For things like serde it might be needed to combine with [field
323 /// attributes](Self::field_attribute).
324 ///
325 /// # Examples
326 ///
327 /// ```rust
328 /// # let mut config = prost_build::Config::new();
329 /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
330 /// config.enum_attribute(".", "#[derive(Eq)]");
331 /// // Some messages want to be serializable with serde as well.
332 /// config.enum_attribute("my_messages.MyEnumType",
333 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
334 /// config.enum_attribute("my_messages.MyMessageType.MyNestedEnumType",
335 /// "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
336 /// ```
337 ///
338 /// # Oneof fields
339 ///
340 /// The `oneof` fields don't have a type name of their own inside Protobuf. Therefore, the
341 /// field name can be used both with `enum_attribute` and `field_attribute` ‒ the first is
342 /// placed before the `enum` type definition, the other before the field inside corresponding
343 /// message `struct`.
344 ///
345 /// In other words, to place an attribute on the `enum` implementing the `oneof`, the match
346 /// would look like `my_messages.MyNestedMessageType.oneofname`.
347 pub fn enum_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
348 where
349 P: AsRef<str>,
350 A: AsRef<str>,
351 {
352 self.enum_attributes
353 .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
354 self
355 }
356
357 /// Wrap matched fields in a `Box`.
358 ///
359 /// # Arguments
360 ///
361 /// **`path`** - a path matching any number of fields. These fields get the attribute.
362 /// For details about matching fields see [`btree_map`](Self::btree_map).
363 ///
364 /// # Examples
365 ///
366 /// ```rust
367 /// # let mut config = prost_build::Config::new();
368 /// config.boxed(".my_messages.MyMessageType.my_field");
369 /// ```
370 pub fn boxed<P>(&mut self, path: P) -> &mut Self
371 where
372 P: AsRef<str>,
373 {
374 self.boxed.insert(path.as_ref().to_string(), ());
375 self
376 }
377
378 /// Configures the code generator to use the provided service generator.
379 pub fn service_generator(&mut self, service_generator: Box<dyn ServiceGenerator>) -> &mut Self {
380 self.service_generator = Some(service_generator);
381 self
382 }
383
384 /// Configures the code generator to not use the `prost_types` crate for Protobuf well-known
385 /// types, and instead generate Protobuf well-known types from their `.proto` definitions.
386 pub fn compile_well_known_types(&mut self) -> &mut Self {
387 self.prost_types = false;
388 self
389 }
390
391 /// Configures the code generator to omit documentation comments on generated Protobuf types.
392 ///
393 /// # Example
394 ///
395 /// Occasionally `.proto` files contain code blocks which are not valid Rust. To avoid doctest
396 /// failures, annotate the invalid code blocks with an [`ignore` or `no_run` attribute][1], or
397 /// disable doctests for the crate with a [Cargo.toml entry][2]. If neither of these options
398 /// are possible, then omit comments on generated code during doctest builds:
399 ///
400 /// ```rust,no_run
401 /// # fn main() -> std::io::Result<()> {
402 /// let mut config = prost_build::Config::new();
403 /// config.disable_comments(&["."]);
404 /// config.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
405 /// # Ok(())
406 /// # }
407 /// ```
408 ///
409 /// As with other options which take a set of paths, comments can be disabled on a per-package
410 /// or per-symbol basis.
411 ///
412 /// [1]: https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#attributes
413 /// [2]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target
414 pub fn disable_comments<I, S>(&mut self, paths: I) -> &mut Self
415 where
416 I: IntoIterator<Item = S>,
417 S: AsRef<str>,
418 {
419 self.disable_comments.clear();
420 for matcher in paths {
421 self.disable_comments
422 .insert(matcher.as_ref().to_string(), ());
423 }
424 self
425 }
426
427 /// Skips generating `impl Debug` for types
428 pub fn skip_debug<I, S>(&mut self, paths: I) -> &mut Self
429 where
430 I: IntoIterator<Item = S>,
431 S: AsRef<str>,
432 {
433 self.skip_debug.clear();
434 for matcher in paths {
435 self.skip_debug.insert(matcher.as_ref().to_string(), ());
436 }
437 self
438 }
439
440 /// Declare an externally provided Protobuf package or type.
441 ///
442 /// `extern_path` allows `prost` types in external crates to be referenced in generated code.
443 ///
444 /// When `prost` compiles a `.proto` which includes an import of another `.proto`, it will
445 /// automatically recursively compile the imported file as well. `extern_path` can be used
446 /// to instead substitute types from an external crate.
447 ///
448 /// # Example
449 ///
450 /// As an example, consider a crate, `uuid`, with a `prost`-generated `Uuid` type:
451 ///
452 /// ```proto
453 /// // uuid.proto
454 ///
455 /// syntax = "proto3";
456 /// package uuid;
457 ///
458 /// message Uuid {
459 /// string uuid_str = 1;
460 /// }
461 /// ```
462 ///
463 /// The `uuid` crate implements some traits for `Uuid`, and publicly exports it:
464 ///
465 /// ```rust,ignore
466 /// // lib.rs in the uuid crate
467 ///
468 /// include!(concat!(env!("OUT_DIR"), "/uuid.rs"));
469 ///
470 /// pub trait DoSomething {
471 /// fn do_it(&self);
472 /// }
473 ///
474 /// impl DoSomething for Uuid {
475 /// fn do_it(&self) {
476 /// println!("Done");
477 /// }
478 /// }
479 /// ```
480 ///
481 /// A separate crate, `my_application`, uses `prost` to generate message types which reference
482 /// `Uuid`:
483 ///
484 /// ```proto
485 /// // my_application.proto
486 ///
487 /// syntax = "proto3";
488 /// package my_application;
489 ///
490 /// import "uuid.proto";
491 ///
492 /// message MyMessage {
493 /// uuid.Uuid message_id = 1;
494 /// string some_payload = 2;
495 /// }
496 /// ```
497 ///
498 /// Additionally, `my_application` depends on the trait impls provided by the `uuid` crate:
499 ///
500 /// ```rust,ignore
501 /// // `main.rs` of `my_application`
502 ///
503 /// use uuid::{DoSomething, Uuid};
504 ///
505 /// include!(concat!(env!("OUT_DIR"), "/my_application.rs"));
506 ///
507 /// pub fn process_message(msg: MyMessage) {
508 /// if let Some(uuid) = msg.message_id {
509 /// uuid.do_it();
510 /// }
511 /// }
512 /// ```
513 ///
514 /// Without configuring `uuid` as an external path in `my_application`'s `build.rs`, `prost`
515 /// would compile a completely separate version of the `Uuid` type, and `process_message` would
516 /// fail to compile. However, if `my_application` configures `uuid` as an extern path with a
517 /// call to `.extern_path(".uuid", "::uuid")`, `prost` will use the external type instead of
518 /// compiling a new version of `Uuid`. Note that the configuration could also be specified as
519 /// `.extern_path(".uuid.Uuid", "::uuid::Uuid")` if only the `Uuid` type were externally
520 /// provided, and not the whole `uuid` package.
521 ///
522 /// # Usage
523 ///
524 /// `extern_path` takes a fully-qualified Protobuf path, and the corresponding Rust path that
525 /// it will be substituted with in generated code. The Protobuf path can refer to a package or
526 /// a type, and the Rust path should correspondingly refer to a Rust module or type.
527 ///
528 /// ```rust
529 /// # let mut config = prost_build::Config::new();
530 /// // Declare the `uuid` Protobuf package and all nested packages and types as externally
531 /// // provided by the `uuid` crate.
532 /// config.extern_path(".uuid", "::uuid");
533 ///
534 /// // Declare the `foo.bar.baz` Protobuf package and all nested packages and types as
535 /// // externally provided by the `foo_bar_baz` crate.
536 /// config.extern_path(".foo.bar.baz", "::foo_bar_baz");
537 ///
538 /// // Declare the `uuid.Uuid` Protobuf type (and all nested types) as externally provided
539 /// // by the `uuid` crate's `Uuid` type.
540 /// config.extern_path(".uuid.Uuid", "::uuid::Uuid");
541 /// ```
542 pub fn extern_path<P1, P2>(&mut self, proto_path: P1, rust_path: P2) -> &mut Self
543 where
544 P1: Into<String>,
545 P2: Into<String>,
546 {
547 self.extern_paths
548 .push((proto_path.into(), rust_path.into()));
549 self
550 }
551
552 /// When set, the `FileDescriptorSet` generated by `protoc` is written to the provided
553 /// filesystem path.
554 ///
555 /// This option can be used in conjunction with the [`include_bytes!`] macro and the types in
556 /// the `prost-types` crate for implementing reflection capabilities, among other things.
557 ///
558 /// ## Example
559 ///
560 /// In `build.rs`:
561 ///
562 /// ```rust, no_run
563 /// # use std::env;
564 /// # use std::path::PathBuf;
565 /// # let mut config = prost_build::Config::new();
566 /// config.file_descriptor_set_path(
567 /// PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set"))
568 /// .join("file_descriptor_set.bin"));
569 /// ```
570 ///
571 /// In `lib.rs`:
572 ///
573 /// ```rust,ignore
574 /// let file_descriptor_set_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin"));
575 /// let file_descriptor_set = prost_types::FileDescriptorSet::decode(&file_descriptor_set_bytes[..]).unwrap();
576 /// ```
577 pub fn file_descriptor_set_path<P>(&mut self, path: P) -> &mut Self
578 where
579 P: Into<PathBuf>,
580 {
581 self.file_descriptor_set_path = Some(path.into());
582 self
583 }
584
585 /// In combination with `file_descriptor_set_path`, this can be used to provide a file
586 /// descriptor set as an input file, rather than having prost-build generate the file by calling
587 /// protoc.
588 ///
589 /// In `build.rs`:
590 ///
591 /// ```rust
592 /// # let mut config = prost_build::Config::new();
593 /// config.file_descriptor_set_path("path/from/build/system")
594 /// .skip_protoc_run()
595 /// .compile_protos(&["src/items.proto"], &["src/"]);
596 /// ```
597 ///
598 pub fn skip_protoc_run(&mut self) -> &mut Self {
599 self.skip_protoc_run = true;
600 self
601 }
602
603 /// Configures the code generator to remove surrounding comments and documentation.
604 ///
605 /// If enabled, this will cause `protoc` to not be passed the `--include_source_info` argument.
606 /// Typically, `--include_source_info` is passed by default, but it results in larger
607 /// [`FileDescriptorSet`s](https://github.com/protocolbuffers/protobuf/blob/cff254d32f850ba8186227ce6775b3f01a1f8cf8/src/google/protobuf/descriptor.proto#L54-L66) that include information about the
608 /// original location of each declaration in the source file as well as surrounding
609 /// comments and documentation.
610 ///
611 /// In `build.rs`:
612 ///
613 /// ```rust
614 /// # let mut config = prost_build::Config::new();
615 /// config.file_descriptor_set_path("path/from/build/system")
616 /// .skip_source_info()
617 /// .compile_protos(&["src/items.proto"], &["src/"]);
618 /// ```
619 pub fn skip_source_info(&mut self) -> &mut Self {
620 self.skip_source_info = true;
621 self
622 }
623
624 /// Configures the code generator to not strip the enum name from variant names.
625 ///
626 /// Protobuf enum definitions commonly include the enum name as a prefix of every variant name.
627 /// This style is non-idiomatic in Rust, so by default `prost` strips the enum name prefix from
628 /// variants which include it. Configuring this option prevents `prost` from stripping the
629 /// prefix.
630 pub fn retain_enum_prefix(&mut self) -> &mut Self {
631 self.strip_enum_prefix = false;
632 self
633 }
634
635 /// Configures the output directory where generated Rust files will be written.
636 ///
637 /// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
638 /// executing build scripts, so `out_dir` typically does not need to be configured.
639 pub fn out_dir<P>(&mut self, path: P) -> &mut Self
640 where
641 P: Into<PathBuf>,
642 {
643 self.out_dir = Some(path.into());
644 self
645 }
646
647 /// Configures what filename protobufs with no package definition are written to.
648 /// The filename will be appended with the `.rs` extension.
649 pub fn default_package_filename<S>(&mut self, filename: S) -> &mut Self
650 where
651 S: Into<String>,
652 {
653 self.default_package_filename = filename.into();
654 self
655 }
656
657 /// Configures the code generator to include type names.
658 ///
659 /// Message types will implement `Name` trait, which provides type and package name.
660 /// This is needed for encoding messages as `Any` type.
661 pub fn enable_type_names(&mut self) -> &mut Self {
662 self.enable_type_names = true;
663 self
664 }
665
666 /// Specify domain names to use with message type URLs.
667 ///
668 /// # Domains
669 ///
670 /// **`paths`** - a path matching any number of types. It works the same way as in
671 /// [`btree_map`](Self::btree_map), just with the field name omitted.
672 ///
673 /// **`domain`** - an arbitrary string to be used as a prefix for type URLs.
674 ///
675 /// # Examples
676 ///
677 /// ```rust
678 /// # let mut config = prost_build::Config::new();
679 /// // Full type URL of the message `google.profile.Person`,
680 /// // will be `type.googleapis.com/google.profile.Person`.
681 /// config.type_name_domain(&["."], "type.googleapis.com");
682 /// ```
683 pub fn type_name_domain<I, S, D>(&mut self, paths: I, domain: D) -> &mut Self
684 where
685 I: IntoIterator<Item = S>,
686 S: AsRef<str>,
687 D: AsRef<str>,
688 {
689 for matcher in paths {
690 self.type_name_domains
691 .insert(matcher.as_ref().to_string(), domain.as_ref().to_string());
692 }
693 self
694 }
695
696 /// Configures the path that's used for deriving `Message` for generated messages.
697 /// This is mainly useful for generating crates that wish to re-export prost.
698 /// Defaults to `::prost` if not specified.
699 pub fn prost_path<S>(&mut self, path: S) -> &mut Self
700 where
701 S: Into<String>,
702 {
703 self.prost_path = Some(path.into());
704 self
705 }
706
707 /// Configures the path that's used well known types.
708 /// This is mainly useful for generating crates that wish to re-export prost_types.
709 /// Defaults to `::prost_types` if not specified.`
710 pub fn prost_types_path<S>(&mut self, path: S) -> &mut Self
711 where
712 S: Into<String>,
713 {
714 self.prost_types_path = Some(path.into());
715 self
716 }
717
718 /// Add an argument to the `protoc` protobuf compilation invocation.
719 ///
720 /// # Example `build.rs`
721 ///
722 /// ```rust,no_run
723 /// # use std::io::Result;
724 /// fn main() -> Result<()> {
725 /// let mut prost_build = prost_build::Config::new();
726 /// // Enable a protoc experimental feature.
727 /// prost_build.protoc_arg("--experimental_allow_proto3_optional");
728 /// prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
729 /// Ok(())
730 /// }
731 /// ```
732 pub fn protoc_arg<S>(&mut self, arg: S) -> &mut Self
733 where
734 S: AsRef<OsStr>,
735 {
736 self.protoc_args.push(arg.as_ref().to_owned());
737 self
738 }
739
740 /// Set the path to `protoc` executable to be used by `prost-build`
741 ///
742 /// Use the provided path to find `protoc`. This can either be a file name which is
743 /// searched for in the `PATH` or an absolute path to use a specific executable.
744 ///
745 /// # Example `build.rs`
746 ///
747 /// ```rust,no_run
748 /// # use std::io::Result;
749 /// fn main() -> Result<()> {
750 /// let mut prost_build = prost_build::Config::new();
751 /// prost_build.protoc_executable("protoc-27.1");
752 /// prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
753 /// Ok(())
754 /// }
755 /// ```
756 pub fn protoc_executable<S>(&mut self, executable: S) -> &mut Self
757 where
758 S: Into<PathBuf>,
759 {
760 self.protoc_executable = executable.into();
761 self
762 }
763
764 /// Configures the optional module filename for easy inclusion of all generated Rust files
765 ///
766 /// If set, generates a file (inside the `OUT_DIR` or `out_dir()` as appropriate) which contains
767 /// a set of `pub mod XXX` statements combining to load all Rust files generated. This can allow
768 /// for a shortcut where multiple related proto files have been compiled together resulting in
769 /// a semi-complex set of includes.
770 ///
771 /// Turning a need for:
772 ///
773 /// ```rust,no_run,ignore
774 /// pub mod Foo {
775 /// pub mod Bar {
776 /// include!(concat!(env!("OUT_DIR"), "/foo.bar.rs"));
777 /// }
778 /// pub mod Baz {
779 /// include!(concat!(env!("OUT_DIR"), "/foo.baz.rs"));
780 /// }
781 /// }
782 /// ```
783 ///
784 /// Into the simpler:
785 ///
786 /// ```rust,no_run,ignore
787 /// include!(concat!(env!("OUT_DIR"), "/_includes.rs"));
788 /// ```
789 pub fn include_file<P>(&mut self, path: P) -> &mut Self
790 where
791 P: Into<PathBuf>,
792 {
793 self.include_file = Some(path.into());
794 self
795 }
796
797 // IMPROVEMENT: https://github.com/tokio-rs/prost/pull/1022/files#r1563818651
798 /// Configures the code generator to format the output code via `prettyplease`.
799 ///
800 /// By default, this is enabled but if the `format` feature is not enabled this does
801 /// nothing.
802 #[cfg(feature = "format")]
803 pub fn format(&mut self, enabled: bool) -> &mut Self {
804 self.fmt = enabled;
805 self
806 }
807
808 /// Compile a [`FileDescriptorSet`] into Rust files during a Cargo build with
809 /// additional code generator configuration options.
810 ///
811 /// This method is like `compile_protos` function except it does not invoke `protoc`
812 /// and instead requires the user to supply a [`FileDescriptorSet`].
813 ///
814 /// # Example `build.rs`
815 ///
816 /// ```rust,no_run
817 /// # use prost_types::FileDescriptorSet;
818 /// # fn fds() -> FileDescriptorSet { todo!() }
819 /// fn main() -> std::io::Result<()> {
820 /// let file_descriptor_set = fds();
821 ///
822 /// prost_build::Config::new()
823 /// .compile_fds(file_descriptor_set)
824 /// }
825 /// ```
826 pub fn compile_fds(&mut self, fds: FileDescriptorSet) -> Result<()> {
827 let mut target_is_env = false;
828 let target: PathBuf = self.out_dir.clone().map(Ok).unwrap_or_else(|| {
829 env::var_os("OUT_DIR")
830 .ok_or_else(|| {
831 Error::new(ErrorKind::Other, "OUT_DIR environment variable is not set")
832 })
833 .map(|val| {
834 target_is_env = true;
835 Into::into(val)
836 })
837 })?;
838
839 let requests = fds
840 .file
841 .into_iter()
842 .map(|descriptor| {
843 (
844 Module::from_protobuf_package_name(descriptor.package()),
845 descriptor,
846 )
847 })
848 .collect::<Vec<_>>();
849
850 let file_names = requests
851 .iter()
852 .map(|req| {
853 (
854 req.0.clone(),
855 req.0.to_file_name_or(&self.default_package_filename),
856 )
857 })
858 .collect::<HashMap<Module, String>>();
859
860 let modules = self.generate(requests)?;
861 for (module, content) in &modules {
862 let file_name = file_names
863 .get(module)
864 .expect("every module should have a filename");
865 let output_path = target.join(file_name);
866
867 write_file_if_changed(&output_path, content.as_bytes())?;
868 }
869
870 if let Some(ref include_file) = self.include_file {
871 let path = target.join(include_file);
872 trace!("Writing include file: {}", path.display());
873 let mut buffer = Vec::new();
874 self.write_line(&mut buffer, 0, "// This file is @generated by prost-build.")?;
875 self.write_includes(
876 modules.keys().collect(),
877 &mut buffer,
878 if target_is_env { None } else { Some(&target) },
879 &file_names,
880 )?;
881
882 write_file_if_changed(&path, &buffer)?;
883 }
884
885 Ok(())
886 }
887
888 /// Loads `.proto` files as a [`FileDescriptorSet`]. This allows inspection of the descriptors
889 /// before calling [`Config::compile_fds`]. This could be used to change [`Config`]
890 /// attributes after introspecting what is actually present in the `.proto` files.
891 ///
892 /// # Example `build.rs`
893 ///
894 /// ```rust,no_run
895 /// # use prost_types::FileDescriptorSet;
896 /// # use prost_build::Config;
897 /// fn main() -> std::io::Result<()> {
898 /// let mut config = Config::new();
899 /// let file_descriptor_set = config.load_fds(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
900 ///
901 /// // Add custom attributes to messages that are service inputs or outputs.
902 /// for file in &file_descriptor_set.file {
903 /// for service in &file.service {
904 /// for method in &service.method {
905 /// if let Some(input) = &method.input_type {
906 /// config.message_attribute(input, "#[derive(custom_proto::Input)]");
907 /// }
908 /// if let Some(output) = &method.output_type {
909 /// config.message_attribute(output, "#[derive(custom_proto::Output)]");
910 /// }
911 /// }
912 /// }
913 /// }
914 ///
915 /// config.compile_fds(file_descriptor_set)
916 /// }
917 /// ```
918 pub fn load_fds(
919 &mut self,
920 protos: &[impl AsRef<Path>],
921 includes: &[impl AsRef<Path>],
922 ) -> Result<FileDescriptorSet> {
923 let tmp;
924 let file_descriptor_set_path = if let Some(path) = &self.file_descriptor_set_path {
925 path.clone()
926 } else {
927 if self.skip_protoc_run {
928 return Err(Error::new(
929 ErrorKind::Other,
930 "file_descriptor_set_path is required with skip_protoc_run",
931 ));
932 }
933 tmp = tempfile::Builder::new().prefix("prost-build").tempdir()?;
934 tmp.path().join("prost-descriptor-set")
935 };
936
937 if !self.skip_protoc_run {
938 let mut cmd = Command::new(&self.protoc_executable);
939 cmd.arg("--include_imports");
940 if !self.skip_source_info {
941 cmd.arg("--include_source_info");
942 }
943 cmd.arg("-o").arg(&file_descriptor_set_path);
944
945 for include in includes {
946 if include.as_ref().exists() {
947 cmd.arg("-I").arg(include.as_ref());
948 } else {
949 debug!(
950 "ignoring {} since it does not exist.",
951 include.as_ref().display()
952 )
953 }
954 }
955
956 // Set the protoc include after the user includes in case the user wants to
957 // override one of the built-in .protos.
958 if let Some(protoc_include) = protoc_include_from_env() {
959 cmd.arg("-I").arg(protoc_include);
960 }
961
962 for arg in &self.protoc_args {
963 cmd.arg(arg);
964 }
965
966 for proto in protos {
967 cmd.arg(proto.as_ref());
968 }
969
970 debug!("Running: {cmd:?}");
971
972 let output = match cmd.output() {
973 Err(err) if ErrorKind::NotFound == err.kind() => return Err(Error::new(
974 err.kind(),
975 error_message_protoc_not_found()
976 )),
977 Err(err) => return Err(Error::new(
978 err.kind(),
979 format!("failed to invoke protoc (hint: https://docs.rs/prost-build/#sourcing-protoc): (path: {}): {}", &self.protoc_executable.display(), err),
980 )),
981 Ok(output) => output,
982 };
983
984 if !output.status.success() {
985 return Err(Error::new(
986 ErrorKind::Other,
987 format!("protoc failed: {}", String::from_utf8_lossy(&output.stderr)),
988 ));
989 }
990 }
991
992 let buf = fs::read(&file_descriptor_set_path).map_err(|e| {
993 Error::new(
994 e.kind(),
995 format!(
996 "unable to open file_descriptor_set_path: {}, OS: {}",
997 file_descriptor_set_path.display(),
998 e
999 ),
1000 )
1001 })?;
1002 let file_descriptor_set = FileDescriptorSet::decode(buf.as_slice()).map_err(|error| {
1003 Error::new(
1004 ErrorKind::InvalidInput,
1005 format!("invalid FileDescriptorSet: {error}"),
1006 )
1007 })?;
1008
1009 Ok(file_descriptor_set)
1010 }
1011
1012 /// Compile `.proto` files into Rust files during a Cargo build with additional code generator
1013 /// configuration options.
1014 ///
1015 /// This method is like the `prost_build::compile_protos` function, with the added ability to
1016 /// specify non-default code generation options. See that function for more information about
1017 /// the arguments and generated outputs.
1018 ///
1019 /// The `protos` and `includes` arguments are ignored if `skip_protoc_run` is specified.
1020 ///
1021 /// # Example `build.rs`
1022 ///
1023 /// ```rust,no_run
1024 /// # use std::io::Result;
1025 /// fn main() -> Result<()> {
1026 /// let mut prost_build = prost_build::Config::new();
1027 /// prost_build.btree_map(&["."]);
1028 /// prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
1029 /// Ok(())
1030 /// }
1031 /// ```
1032 pub fn compile_protos(
1033 &mut self,
1034 protos: &[impl AsRef<Path>],
1035 includes: &[impl AsRef<Path>],
1036 ) -> Result<()> {
1037 // TODO: This should probably emit 'rerun-if-changed=PATH' directives for cargo, however
1038 // according to [1] if any are output then those paths replace the default crate root,
1039 // which is undesirable. Figure out how to do it in an additive way; perhaps gcc-rs has
1040 // this figured out.
1041 // [1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
1042
1043 let file_descriptor_set = self.load_fds(protos, includes)?;
1044
1045 self.compile_fds(file_descriptor_set)
1046 }
1047
1048 pub(crate) fn prost_path_or_default(&self) -> &str {
1049 self.prost_path.as_deref().unwrap_or("::prost")
1050 }
1051
1052 pub(crate) fn prost_types_path_or_default(&self) -> &str {
1053 self.prost_types_path.as_deref().unwrap_or("::prost_types")
1054 }
1055
1056 pub(crate) fn write_includes(
1057 &self,
1058 mut modules: Vec<&Module>,
1059 outfile: &mut impl Write,
1060 basepath: Option<&PathBuf>,
1061 file_names: &HashMap<Module, String>,
1062 ) -> Result<()> {
1063 modules.sort();
1064
1065 let mut stack = Vec::new();
1066
1067 for module in modules {
1068 while !module.starts_with(&stack) {
1069 stack.pop();
1070 self.write_line(outfile, stack.len(), "}")?;
1071 }
1072 while stack.len() < module.len() {
1073 self.write_line(
1074 outfile,
1075 stack.len(),
1076 &format!("pub mod {} {{", module.part(stack.len())),
1077 )?;
1078 stack.push(module.part(stack.len()).to_owned());
1079 }
1080
1081 let file_name = file_names
1082 .get(module)
1083 .expect("every module should have a filename");
1084
1085 if basepath.is_some() {
1086 self.write_line(outfile, stack.len(), &format!("include!(\"{file_name}\");"))?;
1087 } else {
1088 self.write_line(
1089 outfile,
1090 stack.len(),
1091 &format!("include!(concat!(env!(\"OUT_DIR\"), \"/{file_name}\"));"),
1092 )?;
1093 }
1094 }
1095
1096 for depth in (0..stack.len()).rev() {
1097 self.write_line(outfile, depth, "}")?;
1098 }
1099
1100 Ok(())
1101 }
1102
1103 fn write_line(&self, outfile: &mut impl Write, depth: usize, line: &str) -> Result<()> {
1104 outfile.write_all(format!("{}{}\n", (" ").to_owned().repeat(depth), line).as_bytes())
1105 }
1106
1107 /// Processes a set of modules and file descriptors, returning a map of modules to generated
1108 /// code contents.
1109 ///
1110 /// This is generally used when control over the output should not be managed by Prost,
1111 /// such as in a flow for a `protoc` code generating plugin. When compiling as part of a
1112 /// `build.rs` file, instead use [`Self::compile_protos()`].
1113 pub fn generate(
1114 &mut self,
1115 requests: Vec<(Module, FileDescriptorProto)>,
1116 ) -> Result<HashMap<Module, String>> {
1117 let mut modules = HashMap::new();
1118 let mut packages = HashMap::new();
1119
1120 let message_graph = MessageGraph::new(requests.iter().map(|x| &x.1));
1121 let extern_paths = ExternPaths::new(
1122 &self.extern_paths,
1123 self.prost_path_or_default(),
1124 self.prost_types_path_or_default(),
1125 self.prost_types,
1126 )
1127 .map_err(|error| Error::new(ErrorKind::InvalidInput, error))?;
1128 let mut context = Context::new(self, message_graph, extern_paths);
1129
1130 for (request_module, request_fd) in requests {
1131 // Only record packages that have services
1132 if !request_fd.service.is_empty() {
1133 packages.insert(request_module.clone(), request_fd.package().to_string());
1134 }
1135 let buf = modules
1136 .entry(request_module.clone())
1137 .or_insert_with(String::new);
1138 CodeGenerator::generate(&mut context, request_fd, buf);
1139 if buf.is_empty() {
1140 // Did not generate any code, remove from list to avoid inclusion in include file or output file list
1141 modules.remove(&request_module);
1142 }
1143 }
1144
1145 if let Some(service_generator) = context.service_generator_mut() {
1146 for (module, package) in packages {
1147 let buf = modules.get_mut(&module).unwrap();
1148 service_generator.finalize_package(&package, buf);
1149 }
1150 }
1151
1152 #[cfg(feature = "format")]
1153 if self.fmt {
1154 for buf in modules.values_mut() {
1155 let file = syn::parse_file(buf).unwrap();
1156 let formatted = prettyplease::unparse(&file);
1157 *buf = formatted;
1158 }
1159 }
1160
1161 self.add_generated_modules(&mut modules);
1162
1163 Ok(modules)
1164 }
1165
1166 fn add_generated_modules(&mut self, modules: &mut HashMap<Module, String>) {
1167 for buf in modules.values_mut() {
1168 let with_generated = "// This file is @generated by prost-build.\n".to_string() + buf;
1169 *buf = with_generated;
1170 }
1171 }
1172}
1173
1174/// Write a slice as the entire contents of a file.
1175///
1176/// This function will create a file if it does not exist,
1177/// and will entirely replace its contents if it does. When
1178/// the contents is already correct, it doesn't touch to the file.
1179fn write_file_if_changed(path: &Path, content: &[u8]) -> std::io::Result<()> {
1180 let previous_content = fs::read(path);
1181
1182 if previous_content
1183 .map(|previous_content| previous_content == content)
1184 .unwrap_or(false)
1185 {
1186 trace!("unchanged: {}", path.display());
1187 Ok(())
1188 } else {
1189 trace!("writing: {}", path.display());
1190 fs::write(path, content)
1191 }
1192}
1193
1194impl default::Default for Config {
1195 fn default() -> Config {
1196 Config {
1197 file_descriptor_set_path: None,
1198 service_generator: None,
1199 map_type: PathMap::default(),
1200 bytes_type: PathMap::default(),
1201 type_attributes: PathMap::default(),
1202 message_attributes: PathMap::default(),
1203 enum_attributes: PathMap::default(),
1204 field_attributes: PathMap::default(),
1205 boxed: PathMap::default(),
1206 prost_types: true,
1207 strip_enum_prefix: true,
1208 out_dir: None,
1209 extern_paths: Vec::new(),
1210 default_package_filename: "_".to_string(),
1211 enable_type_names: false,
1212 type_name_domains: PathMap::default(),
1213 protoc_args: Vec::new(),
1214 protoc_executable: protoc_from_env(),
1215 disable_comments: PathMap::default(),
1216 skip_debug: PathMap::default(),
1217 skip_protoc_run: false,
1218 skip_source_info: false,
1219 include_file: None,
1220 prost_path: None,
1221 prost_types_path: None,
1222 #[cfg(feature = "format")]
1223 fmt: true,
1224 }
1225 }
1226}
1227
1228impl fmt::Debug for Config {
1229 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
1230 fmt.debug_struct("Config")
1231 .field("file_descriptor_set_path", &self.file_descriptor_set_path)
1232 .field("service_generator", &self.service_generator.is_some())
1233 .field("map_type", &self.map_type)
1234 .field("bytes_type", &self.bytes_type)
1235 .field("type_attributes", &self.type_attributes)
1236 .field("field_attributes", &self.field_attributes)
1237 .field("prost_types", &self.prost_types)
1238 .field("strip_enum_prefix", &self.strip_enum_prefix)
1239 .field("out_dir", &self.out_dir)
1240 .field("extern_paths", &self.extern_paths)
1241 .field("default_package_filename", &self.default_package_filename)
1242 .field("enable_type_names", &self.enable_type_names)
1243 .field("type_name_domains", &self.type_name_domains)
1244 .field("protoc_args", &self.protoc_args)
1245 .field("disable_comments", &self.disable_comments)
1246 .field("skip_debug", &self.skip_debug)
1247 .field("prost_path", &self.prost_path)
1248 .finish()
1249 }
1250}
1251
1252pub fn error_message_protoc_not_found() -> String {
1253 let error_msg = "Could not find `protoc`. If `protoc` is installed, try setting the `PROTOC` environment variable to the path of the `protoc` binary.";
1254
1255 let os_specific_hint = if cfg!(target_os = "macos") {
1256 "To install it on macOS, run `brew install protobuf`."
1257 } else if cfg!(target_os = "linux") {
1258 "To install it on Debian, run `apt-get install protobuf-compiler`."
1259 } else {
1260 "Try installing `protobuf-compiler` or `protobuf` using your package manager."
1261 };
1262 let download_msg =
1263 "It is also available at https://github.com/protocolbuffers/protobuf/releases";
1264
1265 format!(
1266 "{error_msg} {os_specific_hint} {download_msg} For more information: https://docs.rs/prost-build/#sourcing-protoc"
1267 )
1268}
1269
1270/// Returns the path to the `protoc` binary.
1271pub fn protoc_from_env() -> PathBuf {
1272 env::var_os("PROTOC")
1273 .map(PathBuf::from)
1274 .unwrap_or(PathBuf::from("protoc"))
1275}
1276
1277/// Returns the path to the Protobuf include directory.
1278pub fn protoc_include_from_env() -> Option<PathBuf> {
1279 let protoc_include: PathBuf = env::var_os("PROTOC_INCLUDE")?.into();
1280
1281 if !protoc_include.exists() {
1282 panic!(
1283 "PROTOC_INCLUDE environment variable points to non-existent directory ({})",
1284 protoc_include.display()
1285 );
1286 }
1287 if !protoc_include.is_dir() {
1288 panic!(
1289 "PROTOC_INCLUDE environment variable points to a non-directory file ({})",
1290 protoc_include.display()
1291 );
1292 }
1293
1294 Some(protoc_include)
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299 use super::*;
1300
1301 macro_rules! assert_starts_with {
1302 ($left:expr, $right:expr) => {
1303 match (&$left, &$right) {
1304 (left_val, right_val) => {
1305 if !(left_val.starts_with(right_val)) {
1306 panic!(
1307 "assertion 'starts_with` failed:\nleft: {}\nright: {}",
1308 left_val, right_val
1309 )
1310 }
1311 }
1312 }
1313 };
1314 }
1315
1316 #[test]
1317 fn test_error_protoc_not_found() {
1318 let mut config = Config::new();
1319 config.protoc_executable("path-does-not-exist");
1320
1321 let err = config.load_fds(&[""], &[""]).unwrap_err();
1322 assert_eq!(err.to_string(), error_message_protoc_not_found())
1323 }
1324
1325 #[test]
1326 fn test_error_protoc_not_executable() {
1327 let mut config = Config::new();
1328 config.protoc_executable("src/lib.rs");
1329
1330 let err = config.load_fds(&[""], &[""]).unwrap_err();
1331 assert_starts_with!(err.to_string(), "failed to invoke protoc (hint: https://docs.rs/prost-build/#sourcing-protoc): (path: src/lib.rs): ")
1332 }
1333
1334 #[test]
1335 fn test_error_incorrect_skip_protoc_run() {
1336 let mut config = Config::new();
1337 config.skip_protoc_run();
1338
1339 let err = config.load_fds(&[""], &[""]).unwrap_err();
1340 assert_eq!(
1341 err.to_string(),
1342 "file_descriptor_set_path is required with skip_protoc_run"
1343 )
1344 }
1345
1346 #[test]
1347 fn test_error_protoc_failed() {
1348 let mut config = Config::new();
1349
1350 let err = config.load_fds(&[""], &[""]).unwrap_err();
1351 assert_starts_with!(
1352 err.to_string(),
1353 "protoc failed: You seem to have passed an empty string as one of the arguments to "
1354 )
1355 }
1356
1357 #[test]
1358 fn test_error_non_existing_file_descriptor_set() {
1359 let mut config = Config::new();
1360 config.skip_protoc_run();
1361 config.file_descriptor_set_path("path-does-not-exist");
1362
1363 let err = config.load_fds(&[""], &[""]).unwrap_err();
1364 assert_starts_with!(
1365 err.to_string(),
1366 "unable to open file_descriptor_set_path: path-does-not-exist, OS: "
1367 )
1368 }
1369
1370 #[test]
1371 fn test_error_text_incorrect_file_descriptor_set() {
1372 let mut config = Config::new();
1373 config.skip_protoc_run();
1374 config.file_descriptor_set_path("src/lib.rs");
1375
1376 let err = config.load_fds(&[""], &[""]).unwrap_err();
1377 assert_eq!(
1378 err.to_string(),
1379 "invalid FileDescriptorSet: failed to decode Protobuf message: unexpected end group tag"
1380 )
1381 }
1382
1383 #[test]
1384 fn test_error_unset_out_dir() {
1385 let mut config = Config::new();
1386
1387 let err = config
1388 .compile_fds(FileDescriptorSet::default())
1389 .unwrap_err();
1390 assert_eq!(err.to_string(), "OUT_DIR environment variable is not set")
1391 }
1392}