Squashed 'third_party/rules_rust/' changes from 078c6908f..bf9ddeb7c
bf9ddeb7c Release 0.25.1 (#2049)
db5b2fd65 Update tinyjson (#2050)
6a7872ae3 Fix prost proto packages not sanitizing to valid module names (#2044)
c080d7bfa Moved legacy protobuf rules to `proto/protobuf` (#2043)
1281cc051 Remove debug code. (#2048)
cd126be1f Fix build failure finding crate_roots when mixed with generated sources (#2041)
7f751cddd Consolidate rust_prost_library and fix extension-only proto generation. (#2047)
6118c81f2 Release 0.25.0 (#2042)
a6f29fd07 Add Prost and Tonic rules. (#2033)
9442aed8c fix: `crate_type` more accurately corresponds to CC linking actions (#1975)
4f4e2b17b Re-enable zig example on CI (#2030)
2ded0c2f5 Fix flaky coverage test in CI (#2028)
36f8251f9 Exclude .tmp_git_root from globs (#1948)
ca750fa83 Eliminate Rustfmt action in Bindgen rules. Bindgen can run rustfmt (#2025)
c55ec0cfb Allow sysroots from cc_toolchains to be added to bindgen actions (#2024)
9314b1b0c Release 0.24.1 (#2023)
92ea74ade Making rust_std attr in rust_toolchain mandatory (#1984)
a54b8e14b Update `rust_library_group` to use `DepVariantInfo` (#2022)
47644346b Release v0.24.0 (#2020)
a6b0a7f39 Rust library group (#1848)
bc43f4841 Fix crate_universe's `all_crate_deps` and `aliases` functions failing in case the crate's Cargo.toml has condtional dependencies (#2018)
8f27ec7c5 fix: load cargo manifest without resolving abs path of deps (#2017)
23f99bb63 feature: `target_compatible_with` added to `CommonAttrs` (#1976)
11f8c9875 Make `rust_doc_test` inherit it's crate aliases attribute (#2007)
8e848414d Regenerated crate_universe outputs for all packages (#2011)
1b6365131 Don't use startup:windows (#2012)
e80582e75 Fix thumbv* platform resolution (#2010)
367f90ef0 Update bindgen version to 0.65.1 (#2008)
e6ed5bf90 Release 0.23.0 (#2003)
93b230bb8 Fix code coverage collection. (#2001)
0a14bfbb0 Minor CI and test cleanup (#2004)
3e2ee941a Update bindgen rules to build clang from source. (#1998)
5a1a7577d Split up cargo_build_script tests (#2002)
eb6413e83 Update various bash scripts to pipe errors to stderr (#1999)
affe947ac Update stardoc version (#1997)
7073146f8 Add support for armv8-m (#1993)
73a06f130 Added Rust 1.70.0 (#1991)
23c20a93f Fixes crates_vendor workspace name detection when using bzlmod (#1990)
f5813fa08 Set windows flags in platform-specific bazelrc (#1988)
c1632b5b5 Fix up anchor link (#1987)
56e760487 Fix typo in crate_universe-generated defs.bzl comment (#1981)
94cbe4c2c Symlink in the exec-root so that relative paths will work, unchanged. (#1781)
af8ef62eb Release 0.22.0 (#1974)
4aaa6de30 Allow specifying exec and target compatibility constraints (#1971)
f1b19c394 Update rules_apple in tests (#1972)
937e63399 Add T2 support for x86_64-unknown-none (#1967)
66b1bf165 fix: lld-link (MSVC) fix flags including `-l` prefix (#1958)
285dcbbb9 feature: expose `extra_rustc_flags` and `extra_exec_rustc_flags` at `rust_register_toolchains` (#1959)
0f25cb462 Removed `rust_toolchain.os` in favor of `rust_toolchain.exec_triple`. (#1960)
a2a1109dc Add T2 support for thumbv7em-none-eabi (#1957)
80f0eb488 Support for `no_std` mode (#1934)
99aaf0830 Rename crates_vendor_manifests to cvm to shorten windows path lengths (#1944)
0a57da049 Added tests for build script dependencies to crate_universe (#1943)
caffb0a08 Release 0.21.1 (#1936)
c869a17c7 Fix regression in building zlib (#1935)
24b9dea4f Release 0.21.0 (#1933)
7677c617e Add support for rustc flags to `rust_proto_library` (#1932)
fa304ae48 Updated zlib BUILD file to support darwin-arm64 (#1931)
a86313282 Added Rust 1.69.0 (#1930)
f0e12c707 Make BuildInfo provider public (#1920)
c6ad23aba Respect `#[global_allocator]` in `cc_common.link` builds (#1926)
d78752504 Exclude target directory from release tars (#1922)
0339cd18a [wasm-bindgen] Update to v0.2.84 (#1919)
07af5678e Handle corner case for windows architecture detection (#1915)
c56e7660d Fix optional deps by platform (#1911)
4663ff6a3 cc_common_link: also respect --custom_malloc if set (#1912)
dab425760 Add Rust 1.68.2 (#1908)
e4bd39f95 Add empty rustfmt.toml (#1907)
eaf513865 Support bzlmod (#1528)
1074ecbab Release v0.20.0 (#1900)
44aec0a79 ci: fix test config in cc_common_link_ubuntu2004 (#1904)
6571cde64 Adds per_crate_rustc_flag build setting. (#1827)
7a47449df Added Rust 1.68.1 (#1898)
e3bcdbad1 Fixed rustdoc warnings in crate_universe (#1897)
529f45900 Added `rustdoc_flags` attribute to rust_doc rule (#1867)
9e3499405 Have rustdoc return its output directory instead of zip default. (#1868)
9d6741f40 Implement support for optional crates enabled with dep: features (#1885)
fd10963ea Skip adding -lstatic to libtest and libstd on Darwin (#1620)
b3314b45e Release 0.19.1 (#1895)
c1a9fd86f Accumulate all features from cargo tree output (#1884)
206f71c95 Disable zig example (#1893)
1a5d07cd2 Add runfiles support to rust_stdlib_filegroup (#1890)
6996cd550 Deleted unused targets and cleanup docs (#1889)
a85e24e20 Fix triple constraints for iOS and watchOS (#1888)
e13fd3bad Release rules_rust and cargo-bazel (#1882)
9e9853d63 Add support for thumbv7em with hard float (#1871)
b3cd5962e Added Rust 1.68.0 (#1866)
f1b7aedf5 Support sparse indexes (#1857)
7f2dd433a Make fetch_shas work with mktemp from coreutils 8.32 (#1870)
a9cc01230 Update crate_universe dependencies (#1872)
c038e94ae Pipe stderr from cargo tree processes (#1879)
222d60322 Parallelize cargo tree calls (#1874)
cdbbf7131 Add Fuchsia platform support (#1833)
17e5b04c2 Use `_make_link_flags_darwin` when target os is `ios`. (#1843)
d9ecc8df4 crate_universe: Support fetching crates with git branch, tag or rev (#1846)
1c694cd60 Forward `toolchains` to `cargo_build_script` targets (#1862)
9affcbfa7 Skip detecting abi for empty values (#1830)
6193fe823 Re-enable crate_universe MacOS tests (#1861)
c25db95ae Updated Rust to 1.67.1 (#1864)
7b8fd06be Support `[patch]` in crate_universe when using multiple `Cargo.toml`s (#1856)
c645fe399 Silence windows build failure (#1863)
75bba7b50 Make rust_clippy providers match rustfmt_test (#1806)
f09d72755 Fix test assertion for arm64 macs (#1845)
f4113bfe1 Fix tests for new Apple toolchain (#1844)
20ce44e30 fix: use target_triple struct instead of string (#1835)
bdbded181 Fix code example in doc (#1838)
4f4014052 Fix typo: plced -> placed (#1834)
baeb0664d Remove ios/android/wasm support for gen_rust_project deps (#1684)
02557a47a Add `render_config` attribute to `crates_vendor`. (#1832)
4357fb154 Updated rules_rust to version 0.18.0 (#1829)
9adfdca9b Various cleanups (#1828)
4fa412385 Added update known shas to include T1-T2 triples (#1824)
905731ad9 Instructions on how to perform `rustfmt` check (#1822) (#1823)
108b1a187 Encapsulate running cargo into a struct (#1815)
57a099b63 Fixes resolver issue with root packages and another dependency format (#1819)
78ca9ba0a Use env method recently added to cargo_metadata (#1813)
92834930f Updated `rust_toolchain.target_json` to take encoded json strings (#1810)
84f1d0657 support `resolver = "2"` target-specific features (#1710)
a5853fd37 Use correct dynamic link args fro proc-macro crates (#1803)
b656e2553 Added tests for the `triple` constructor (#1811)
ea4a79ad9 Disable job in CI to avoid infrastructure failure. (#1816)
2fc02f030 Delete `rust_toolchain.rusrc_srcs` (#1807)
804d5fc1f Convert `rust_toolchain` attrs `exec_triple` and `target_triple` to structs (#1808)
499a2ca38 Updated platform triple values from strings to structs ("triple") (#1804)
aae1dbdcb Unify functions for computing constraint values for platform triple abi (#1805)
0d6d2b1eb Updated rules_rust version to `0.17.0` (#1800)
88e83f2df Added Rust 1.67.0 (#1799)
6922b5012 rustdoc_test: fix and test OUT_DIR (#1779)
ad01d1b0e [crate_universe] add an annotation to disable pipelining (#1733)
f651cd18f Add `CARGO_BAZEL_REPIN_ONLY` repinning allowlist (#1798)
d7f0debb0 Revert "Disable broken clang and ldd CI jobs (#1785)" (#1796)
96f82aaad Fix `cc_common.link` file output name (#1795)
5079b64d5 Fix use of `rustfmt_toolchain` when `rustc` is not provided (#1794)
23c650f35 Have `--experimental_use_cc_common_link` cover `rust_shared_library` (#1792)
ba0fb5956 Added support for `--nolegacy_external_runfiles` to `rust_doc_test` (#1790)
112242bb7 Prevent crates_vendor from restarting bazel. (#1791)
52231ef9f Added compatibility flags to `.bazelrc` to prevent regressions (#1789)
91cd399a0 Add "crate-name={}" tag to Crate Universe targets (#1787)
1b1dae196 Added Rust 1.66.1 (#1767)
fe17e8b8e Add file:// prefix to env var in docs (#1788)
0fe742bff Updated `rust_bindgen` to use `rustfmt_toolchain` (#1770)
042fd6c1c Update docs on setting Rust versions (#1786)
dddd8a0d4 Updated crate_universe dependencies (#1775)
a1330a71f Download `rustc` in `rustfmt_toolchain_repository` (#1769)
e96aad9aa Updated the ios_build example to use `crates_vendor` (#1778)
e315007df Disable broken clang and ldd CI jobs (#1785)
4e89d52a9 rustdoc_test: substitute the root of the current crate (#1777)
a52041fb5 Support `target_settings` in `rust_repository_set` and `rust_toolchain_repository` (#1758)
49906eb29 Update clippy and rustfmt aspects to require CrateInfo providers (#1772)
85564208e Updated rules_rust version to `0.16.1` (#1761)
614499a5b Fixed inability to deserialize crate_universe lockfiles (#1760)
9803d3034 Fix data and compile_data for rust_doc (#1741)
927a364cb Update Release github pipeline to trigger automatically (#1757)
7d03e05f8 Fix release pipeline (#1756)
cf7ca5dfd Updated rules_rust to version `0.16.0` (#1750)
203fe4b9a Remove unnecessary binary file (#1755)
941c7cca9 Don't propagate `compatible_with` to the underlying `cargo_build_script` `rust_binary` target (#1754)
a31490d9a Make loads from @rules_rust//rust:defs.bzl come out on one line (#1753)
7ebad4d50 Generate only the needed subset of binaries for bindgen and proto (#1751)
4ef3d4aaa Repin examples/crate_universe_unnamed (#1752)
d6e300359 Regenerate BUILD files using serde_starlark renderer (#1746)
e7c8a97d1 Convert BUILD.$name-$version.bazel to serde_starlark (#1743)
c09818d3b Exclude generated files from language stats and collapse in code review (#1747)
26a24f030 Added CI for single toolchain channel workspaces (#1712)
caed7d814 Report context on error failing to get version (#1744)
36b57af7b Add gen_binaries annotation to control which bins to make target for (#1718)
d916a6f52 crate_universe re-pinning now defaults to "workspace" (#1723)
f34661ee1 Propagate `compatible_with` attribute to the underlying `_build_script_run` target (#1745)
92977d1bf Re-pinned all dependencies managed by crate_universe (#1735)
d5289ad1c Added `rustfmt_toolchain` and refactored toolchain repository rules (#1719)
532e60ff0 Collect targets in a deterministic order (#1736)
52e02c25b Eliminate all use of hash-based collections from crate_universe (#1737)
31073ff8e Replace tera template with serde_starlark (#1734)
d4e5586d0 Support the RUNFILES_DIR environment variable. (#1732)
1357b85b1 Addressed clippy warnings from `clippy 0.1.67 (ec56537c 2022-12-15)` (#1717)
8bc9f788d Support dsym_folder output group in tests (#1703)
90c5b6eb7 Added CI for minimum supported Rust version (#1720)
be82ff8bd Match prerelease versions with annotation wildcard (#1716)
36c7f285b Arm Thumb Embedded Targets. (#1721)
5ef52e465 Update current_toolchain_files tests to use a dedicated test rule (#1714)
c75ea6f9e Add `Runfiles::current_repository` to runfiles library (#1713)
2f0511782 Updated rules_rust to version `0.15.0` (#1706)
019f87178 Added Rust 1.66.0 (#1705)
1469cd7cb Fix labels to work with canonical label literals. (#1700)
5826a500a Add riscv32imc and riscv64gc to the known sha targets (#1698)
40dee95ce Fixed typos: normla -> normal (#1699)
8f08e77ac load_arbitrary_tool uses tool_suburl to look up sha256 (#1695)
8faec3060 Fix typos in crate_universe rendered comments (#1691)
bd64711ff Silence flaky test (#1693)
46b7ea5af Added a build setting for toolchain channels (#1671)
70b272aad Updated rules_rust to version `0.14.0` (#1669)
91e597dd1 Updated all crates_vendor outputs (#1687)
9a047b0b9 Updated crate_universe dependencies (#1686)
3a91d2f5b Add RV64GC target (#1683)
d9e752ab4 Add per-toolchain `rustc_flags` (#1635)
56237415e stardoc: Use backtick not `<code>` for attr default values (#1682)
d4b31a494 Allow passing a bazel path to vendor explicitly (#1661)
d51bf9ce0 Updated crate_universe to work with `--nolegacy_external_runfiles` (#1680)
7f40636d1 crate_universe/private/crates_vendor.bzl typo fix (#1678)
025bf7db8 Merge cc toolchain flags into build script env (#1675)
b7c36c051 Fix confusing/misleading crate_universe docs (#1677)
29233e354 Revert #1564 (#1663)
ed32b6de2 Common glob excludes (#1673)
61b99cdd1 fix: add space to crate data exclude list (#1665)
8bb25b8b7 Support Windows ARM64 (aarch64-pc-windows-msvc) (#1664)
ddf2a4c23 Re-render crate BUILD files after #1647 (#1655)
44c7e1588 Group deps and aliases by platform triple rather than by cfg string when generating BUILD files. This avoid bazel errors due to duplicate keys/deps. (#1647)
de18d8bb6 Allow `buildifier` attribute to be a file (#1660)
aa0815dc9 Fix naming of ambiguous libs (#1625)
ff314d4ab Also pass -c opt to tests in opt mode CI (#1626)
ff4e90515 Reenable windows job (#1658)
c45b8e91f Updated rules_rust to version `0.13.0` (#1644)
87d6b6c37 Update `//util/label` to support `+` in packages (#1654)
ab6959db5 fix: Fix issue with wasi-0.11.0+wasi-snapshot-preview1 (#1632)
28c090ed0 Replaced custom platform constraint values with aliases to `@platforms` (#1652)
dfbea4f52 Deprecated `rust_toolchain.rustc_srcs` (#1653)
fd1db4391 Remove deprecated attributes from rust_toolchain and cargo_bootstrap (#1651)
c8ab970c4 Generated rust-project.json files now include sysroot paths (#1641)
0a3e04cf9 Fix vendoring when not in a package (#1646)
aece1e37d Deduplicate expand_location targets in rust-project.json crate creation to avoid a bazel crash (#1639)
03a0b2483 [docs] Fixing typos in CargoConfig doc strings (#1638)
bd4fd2ac5 Upgraded cfg-expr dependency to 0.12.0. (#1636)
330554a13 Disable failing job in CI (#1640)
849f106e6 Consider compilation mode when choosing `pic`/`nopic` object files to link (#1624)
53491d719 Updated rules_rust to version `0.12.0` (#1630)
8e8843724 Remove empty glob (#1628)
1f621a944 Added Rust 1.65.0 (#1627)
c6af4d025 Add `-c opt` mode to CI (#1621)
95320cc8b process_wrapper: print line on error to parse message as json (#1566)
81eaccf39 Fixed CI breakage (#1619)
478fc3a57 Fix ambiguous native dependencies in `proc_macro`s and `staticlib`s (#1611)
9e3d8415e Build deps of _build_script_run in 'exec' mode (#1561)
ea031082b Fixed outdated docs (#1614)
a8c540e49 Restore support for old cargo_build_script load statements (#1613)
295b5ccc7 Renamed `_build_script_run` rule to `cargo_build_script` (#1612)
3778069ec Remove references to Google mirror in docs (#1607)
aad54ba29 Updated crate_universe dependencies (#1606)
c349df2a6 Remove Google mirror from Starlark snippet in release notes (#1604)
0493b998d Avoid rendering a mock root package when possible (#1596)
b04aa053c process_wrapper: Apply substitutions to param files (#1565)
b209b3e15 Updated rules_rust to version `0.11.0`. (#1587)
b1079453b Typo correction on doc (#1593)
ca5678266 Updated crate_universe dependencies (#1591)
a364d448f Fixes crates_vendor labels in remote mode when used from the root workspace (#1575)
1cc37c268 Expose the output directory from cargo_build_script (#1588)
7ffe0a555 Ignore non-utf8 text in build script output (#1583)
c5b38fe00 Merge runfiles from transitive dependencies of rust_test crate attr (#1487)
da3d522d5 Fix build scripts targeting the wrong architecture (#1564)
d288ed634 Add `out_dir` support in `cargo_dep_env` (#1571)
78d6c1b46 fix: incorrect rustfmt edition query (#1582)
48927127e Set CARGO_MANIFEST_DIR at runtime for tests (#1559)
76bd69033 Add an output group for the .rmeta (#1585)
352bfeb05 Cleanup deprecated code (#1577)
86dc561f9 Move crate_root_src to utils (#1570)
beb554eb9 update to wasm-bindgen v0.2.83 (#1567)
73fd1616b Export AbsoluteLabel functionality (#1568)
c57e7a399 Remap $PWD to empty string instead of "." (#1563)
f0cdcedc2 Added Rust 1.64.0 (#1562)
1d326554a Update docs to show release policies and support (#1560)
78c793a0a Fix markdown typo in rust_analyzer.md (#1553)
c13980fb6 Add iOS examples (#1546)
8a5e07e9f Update apple_support (#1549)
6dacd9803 Strip leading '@'s for labels in the splicing manifest (#1547)
f73d1d6fb use crate_info.deps in establish_cc_info (#1543)
4845af6c0 Add android example (#1545)
9570b7aa7 Remove -lgcc from Android builds (#1541)
cb9ca1b81 Fix crate_universe/private/srcs.bzl to work with repo mappings (#1540)
d1fc9accc Minor cleanup of CI pipelines (#1534)
2bb077b3b Updated rules_rust to version 0.10.0 (#1533)
b8751b860 add cc config info to dummy wasm32 cc toolchain (#1532)
f5ed797ee Updates rust_test to use main.rs as the root when use_libtest_harness is false (#1518)
cfcaf21d5 Preserve directory structure of source files when some are generated (#1526)
51c065841 migrating to rbe_preconfig and remove bazel_toolchains (#1524)
055abd402 Fix typo in an example of crates_repository rule (#1520)
8bfed1cd2 Added Rust 1.63.0 (#1512)
3a69ce09b Update wasm_bindgen to 0.2.82 (#1513)
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf9ddeb7c83a9fe8a7d87c76134cdd8e16131b62
Signed-off-by: Adam Snaider <adsnaider@gmail.com>
Change-Id: Id9490c68d6221da66953a915a25042ef8b848505
diff --git a/proto/prost/private/protoc_wrapper.rs b/proto/prost/private/protoc_wrapper.rs
new file mode 100644
index 0000000..2a0b555
--- /dev/null
+++ b/proto/prost/private/protoc_wrapper.rs
@@ -0,0 +1,1261 @@
+//! A process wrapper for running a Protobuf compiler configured for Prost or Tonic output in a Bazel rule.
+
+use std::collections::BTreeMap;
+use std::collections::BTreeSet;
+use std::fmt::{Display, Formatter, Write};
+use std::fs;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process;
+use std::{env, fmt};
+
+use heck::ToSnakeCase;
+use prost::Message;
+use prost_types::{
+ DescriptorProto, EnumDescriptorProto, FileDescriptorProto, FileDescriptorSet,
+ OneofDescriptorProto,
+};
+
+/// Locate prost outputs in the protoc output directory.
+fn find_generated_rust_files(out_dir: &Path) -> BTreeSet<PathBuf> {
+ let mut all_rs_files: BTreeSet<PathBuf> = BTreeSet::new();
+ for entry in fs::read_dir(out_dir).expect("Failed to read directory") {
+ let entry = entry.expect("Failed to read entry");
+ let path = entry.path();
+ if path.is_dir() {
+ for f in find_generated_rust_files(&path) {
+ all_rs_files.insert(f);
+ }
+ } else if let Some(ext) = path.extension() {
+ if ext == "rs" {
+ all_rs_files.insert(path);
+ }
+ } else if let Some(name) = path.file_name() {
+ // The filename is set to `_` when the package name is empty.
+ if name == "_" {
+ let rs_name = path.parent().expect("Failed to get parent").join("_.rs");
+ fs::rename(&path, &rs_name).unwrap_or_else(|err| {
+ panic!("Failed to rename file: {err:?}: {path:?} -> {rs_name:?}")
+ });
+ all_rs_files.insert(rs_name);
+ }
+ }
+ }
+
+ all_rs_files
+}
+
+fn snake_cased_package_name(package: &str) -> String {
+ if package == "_" {
+ return package.to_owned();
+ }
+
+ package
+ .split('.')
+ .map(|s| s.to_snake_case())
+ .collect::<Vec<_>>()
+ .join(".")
+}
+
+/// Rust module definition.
+#[derive(Debug, Default)]
+struct Module {
+ /// The name of the module.
+ name: String,
+
+ /// The contents of the module.
+ contents: String,
+
+ /// The names of any other modules which are submodules of this module.
+ submodules: BTreeSet<String>,
+}
+
+/// Generate a lib.rs file with all prost/tonic outputs embeeded in modules which
+/// mirror the proto packages. For the example proto file we would expect to see
+/// the Rust output that follows it.
+///
+/// ```proto
+/// syntax = "proto3";
+/// package examples.prost.helloworld;
+///
+/// message HelloRequest {
+/// // Request message contains the name to be greeted
+/// string name = 1;
+/// }
+//
+/// message HelloReply {
+/// // Reply contains the greeting message
+/// string message = 1;
+/// }
+/// ```
+///
+/// This is expected to render out to something like the following. Note that
+/// formatting is not applied so indentation may be missing in the actual output.
+///
+/// ```ignore
+/// pub mod examples {
+/// pub mod prost {
+/// pub mod helloworld {
+/// // @generated
+/// #[allow(clippy::derive_partial_eq_without_eq)]
+/// #[derive(Clone, PartialEq, ::prost::Message)]
+/// pub struct HelloRequest {
+/// /// Request message contains the name to be greeted
+/// #[prost(string, tag = "1")]
+/// pub name: ::prost::alloc::string::String,
+/// }
+/// #[allow(clippy::derive_partial_eq_without_eq)]
+/// #[derive(Clone, PartialEq, ::prost::Message)]
+/// pub struct HelloReply {
+/// /// Reply contains the greeting message
+/// #[prost(string, tag = "1")]
+/// pub message: ::prost::alloc::string::String,
+/// }
+/// // @protoc_insertion_point(module)
+/// }
+/// }
+/// }
+/// ```
+fn generate_lib_rs(prost_outputs: &BTreeSet<PathBuf>, is_tonic: bool) -> String {
+ let mut module_info = BTreeMap::new();
+
+ for path in prost_outputs.iter() {
+ let mut package = path
+ .file_stem()
+ .expect("Failed to get file stem")
+ .to_str()
+ .expect("Failed to convert to str")
+ .to_string();
+
+ if is_tonic {
+ package = package
+ .strip_suffix(".tonic")
+ .expect("Failed to strip suffix")
+ .to_string()
+ };
+
+ if package.is_empty() {
+ continue;
+ }
+
+ let name = if package == "_" {
+ package.clone()
+ } else if package.contains('.') {
+ package
+ .rsplit_once('.')
+ .expect("Failed to split on '.'")
+ .1
+ .to_snake_case()
+ .to_string()
+ } else {
+ package.to_snake_case()
+ };
+
+ // Avoid a stack overflow by skipping a known bad package name
+ let module_name = snake_cased_package_name(&package);
+
+ module_info.insert(
+ module_name.clone(),
+ Module {
+ name,
+ contents: fs::read_to_string(path).expect("Failed to read file"),
+ submodules: BTreeSet::new(),
+ },
+ );
+
+ let module_parts = module_name.split('.').collect::<Vec<_>>();
+ for parent_module_index in 0..module_parts.len() {
+ let child_module_index = parent_module_index + 1;
+ if child_module_index >= module_parts.len() {
+ break;
+ }
+ let full_parent_module_name = module_parts[0..parent_module_index + 1].join(".");
+ let parent_module_name = module_parts[parent_module_index];
+ let child_module_name = module_parts[child_module_index];
+
+ module_info
+ .entry(full_parent_module_name.clone())
+ .and_modify(|parent_module| {
+ parent_module
+ .submodules
+ .insert(child_module_name.to_string());
+ })
+ .or_insert(Module {
+ name: parent_module_name.to_string(),
+ contents: "".to_string(),
+ submodules: [child_module_name.to_string()].iter().cloned().collect(),
+ });
+ }
+ }
+
+ let mut content = "// @generated\n\n".to_string();
+ write_module(&mut content, &module_info, "", 0);
+ content
+}
+
+/// Write out a rust module and all of its submodules.
+fn write_module(
+ content: &mut String,
+ module_info: &BTreeMap<String, Module>,
+ module_name: &str,
+ depth: usize,
+) {
+ if module_name.is_empty() {
+ for submodule_name in module_info.keys() {
+ write_module(content, module_info, submodule_name, depth + 1);
+ }
+ return;
+ }
+ let module = module_info.get(module_name).expect("Failed to get module");
+ let indent = " ".repeat(depth);
+ let is_rust_module = module.name != "_";
+
+ if is_rust_module {
+ let rust_module_name = escape_keyword(module.name.clone());
+ content
+ .write_str(&format!("{}pub mod {} {{\n", indent, rust_module_name))
+ .expect("Failed to write string");
+ }
+
+ content
+ .write_str(&module.contents)
+ .expect("Failed to write string");
+
+ for submodule_name in module.submodules.iter() {
+ write_module(
+ content,
+ module_info,
+ [module_name, submodule_name].join(".").as_str(),
+ depth + 1,
+ );
+ }
+
+ if is_rust_module {
+ content
+ .write_str(&format!("{}}}\n", indent))
+ .expect("Failed to write string");
+ }
+}
+
+/// ProtoPath is a path to a proto message, enum, or oneof.
+///
+/// Example: `helloworld.Greeter.HelloRequest`
+#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
+struct ProtoPath(String);
+
+impl ProtoPath {
+ /// Join a component to the end of the path.
+ fn join(&self, component: &str) -> ProtoPath {
+ if self.0.is_empty() {
+ return ProtoPath(component.to_string());
+ }
+ if component.is_empty() {
+ return self.clone();
+ }
+
+ ProtoPath(format!("{}.{}", self.0, component))
+ }
+}
+
+impl Display for ProtoPath {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl From<&str> for ProtoPath {
+ fn from(path: &str) -> Self {
+ ProtoPath(path.to_string())
+ }
+}
+
+/// RustModulePath is a path to a rust module.
+///
+/// Example: `helloworld::greeter::HelloRequest`
+#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
+struct RustModulePath(String);
+
+impl RustModulePath {
+ /// Join a path to the end of the module path.
+ fn join(&self, path: &str) -> RustModulePath {
+ if self.0.is_empty() {
+ return RustModulePath(path.to_string());
+ }
+ if path.is_empty() {
+ return self.clone();
+ }
+
+ RustModulePath(format!("{}::{}", self.0, path))
+ }
+}
+
+impl Display for RustModulePath {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl From<&str> for RustModulePath {
+ fn from(path: &str) -> Self {
+ RustModulePath(path.to_string())
+ }
+}
+
+/// Compute the `--extern_path` flags for a list of proto files. This is
+/// expected to convert proto files into a BTreeMap of
+/// `example.prost.helloworld`: `crate_name::example::prost::helloworld`.
+fn get_extern_paths(
+ descriptor_set: &FileDescriptorSet,
+ crate_name: &str,
+) -> Result<BTreeMap<ProtoPath, RustModulePath>, String> {
+ let mut extern_paths = BTreeMap::new();
+ let rust_path = RustModulePath(crate_name.to_string());
+
+ for file in descriptor_set.file.iter() {
+ descriptor_set_file_to_extern_paths(&mut extern_paths, &rust_path, file);
+ }
+
+ Ok(extern_paths)
+}
+
+/// Add the extern_path pairs for a file descriptor type.
+fn descriptor_set_file_to_extern_paths(
+ extern_paths: &mut BTreeMap<ProtoPath, RustModulePath>,
+ rust_path: &RustModulePath,
+ file: &FileDescriptorProto,
+) {
+ let package = file.package.clone().unwrap_or_default();
+ let rust_path = rust_path.join(&snake_cased_package_name(&package).replace('.', "::"));
+ let proto_path = ProtoPath(package);
+
+ for message_type in file.message_type.iter() {
+ message_type_to_extern_paths(extern_paths, &proto_path, &rust_path, message_type);
+ }
+
+ for enum_type in file.enum_type.iter() {
+ enum_type_to_extern_paths(extern_paths, &proto_path, &rust_path, enum_type);
+ }
+}
+
+/// Add the extern_path pairs for a message descriptor type.
+fn message_type_to_extern_paths(
+ extern_paths: &mut BTreeMap<ProtoPath, RustModulePath>,
+ proto_path: &ProtoPath,
+ rust_path: &RustModulePath,
+ message_type: &DescriptorProto,
+) {
+ let message_type_name = message_type
+ .name
+ .as_ref()
+ .expect("Failed to get message type name");
+
+ extern_paths.insert(
+ proto_path.join(message_type_name),
+ rust_path.join(message_type_name),
+ );
+
+ let name_lower = message_type_name.to_lowercase();
+ let proto_path = proto_path.join(&name_lower);
+ let rust_path = rust_path.join(&name_lower);
+
+ for nested_type in message_type.nested_type.iter() {
+ message_type_to_extern_paths(extern_paths, &proto_path, &rust_path, nested_type)
+ }
+
+ for enum_type in message_type.enum_type.iter() {
+ enum_type_to_extern_paths(extern_paths, &proto_path, &rust_path, enum_type);
+ }
+
+ for oneof_type in message_type.oneof_decl.iter() {
+ oneof_type_to_extern_paths(extern_paths, &proto_path, &rust_path, oneof_type);
+ }
+}
+
+/// Add the extern_path pairs for an enum type.
+fn enum_type_to_extern_paths(
+ extern_paths: &mut BTreeMap<ProtoPath, RustModulePath>,
+ proto_path: &ProtoPath,
+ rust_path: &RustModulePath,
+ enum_type: &EnumDescriptorProto,
+) {
+ let enum_type_name = enum_type
+ .name
+ .as_ref()
+ .expect("Failed to get enum type name");
+ extern_paths.insert(
+ proto_path.join(enum_type_name),
+ rust_path.join(enum_type_name),
+ );
+}
+
+fn oneof_type_to_extern_paths(
+ extern_paths: &mut BTreeMap<ProtoPath, RustModulePath>,
+ proto_path: &ProtoPath,
+ rust_path: &RustModulePath,
+ oneof_type: &OneofDescriptorProto,
+) {
+ let oneof_type_name = oneof_type
+ .name
+ .as_ref()
+ .expect("Failed to get oneof type name");
+ extern_paths.insert(
+ proto_path.join(oneof_type_name),
+ rust_path.join(oneof_type_name),
+ );
+}
+
+/// The parsed command-line arguments.
+struct Args {
+ /// The path to the protoc binary.
+ protoc: PathBuf,
+
+ /// The path to the output directory.
+ out_dir: PathBuf,
+
+ /// The name of the crate.
+ crate_name: String,
+
+ /// The bazel label.
+ label: String,
+
+ /// The path to the package info file.
+ package_info_file: PathBuf,
+
+ /// The proto files to compile.
+ proto_files: Vec<PathBuf>,
+
+ /// The include directories.
+ includes: Vec<String>,
+
+ /// Dependency descriptor sets.
+ descriptor_set: PathBuf,
+
+ /// The path to the generated lib.rs file.
+ out_librs: PathBuf,
+
+ /// The proto include paths.
+ proto_paths: Vec<String>,
+
+ /// The path to the rustfmt binary.
+ rustfmt: Option<PathBuf>,
+
+ /// Whether to generate tonic code.
+ is_tonic: bool,
+
+ /// Extra arguments to pass to protoc.
+ extra_args: Vec<String>,
+}
+
+impl Args {
+ /// Parse the command-line arguments.
+ fn parse() -> Result<Args, String> {
+ let mut protoc: Option<PathBuf> = None;
+ let mut out_dir: Option<PathBuf> = None;
+ let mut crate_name: Option<String> = None;
+ let mut package_info_file: Option<PathBuf> = None;
+ let mut proto_files: Vec<PathBuf> = Vec::new();
+ let mut includes = Vec::new();
+ let mut descriptor_set = None;
+ let mut out_librs: Option<PathBuf> = None;
+ let mut rustfmt: Option<PathBuf> = None;
+ let mut proto_paths = Vec::new();
+ let mut label: Option<String> = None;
+ let mut tonic_or_prost_opts = Vec::new();
+ let mut is_tonic = false;
+
+ let mut extra_args = Vec::new();
+
+ // Iterate over the given command line arguments parsing out arguments
+ // for the process runner and arguments for protoc and potentially spawn
+ // additional arguments needed by prost.
+ for arg in env::args().skip(1) {
+ if !arg.starts_with('-') {
+ proto_files.push(PathBuf::from(arg));
+ continue;
+ }
+
+ if arg.starts_with("-I") {
+ includes.push(
+ arg.strip_prefix("-I")
+ .expect("Failed to strip -I")
+ .to_string(),
+ );
+ continue;
+ }
+
+ if arg == "--is_tonic" {
+ is_tonic = true;
+ continue;
+ }
+
+ if !arg.contains('=') {
+ extra_args.push(arg);
+ continue;
+ }
+
+ let parts = arg.split_once('=').expect("Failed to split argument on =");
+ match parts {
+ ("--protoc", value) => {
+ protoc = Some(PathBuf::from(value));
+ }
+ ("--prost_out", value) => {
+ out_dir = Some(PathBuf::from(value));
+ }
+ ("--package_info_output", value) => {
+ let (key, value) = value
+ .split_once('=')
+ .map(|(a, b)| (a.to_string(), PathBuf::from(b)))
+ .expect("Failed to parse package info output");
+ crate_name = Some(key);
+ package_info_file = Some(value);
+ }
+ ("--deps_info", value) => {
+ for line in fs::read_to_string(value)
+ .expect("Failed to read file")
+ .lines()
+ {
+ let path = PathBuf::from(line.trim());
+ for flag in fs::read_to_string(path)
+ .expect("Failed to read file")
+ .lines()
+ {
+ tonic_or_prost_opts.push(format!("extern_path={}", flag.trim()));
+ }
+ }
+ }
+ ("--descriptor_set", value) => {
+ descriptor_set = Some(PathBuf::from(value));
+ }
+ ("--out_librs", value) => {
+ out_librs = Some(PathBuf::from(value));
+ }
+ ("--rustfmt", value) => {
+ rustfmt = Some(PathBuf::from(value));
+ }
+ ("--proto_path", value) => {
+ proto_paths.push(value.to_string());
+ }
+ ("--label", value) => {
+ label = Some(value.to_string());
+ }
+ (arg, value) => {
+ extra_args.push(format!("{}={}", arg, value));
+ }
+ }
+ }
+
+ for tonic_or_prost_opt in tonic_or_prost_opts {
+ extra_args.push(format!("--prost_opt={}", tonic_or_prost_opt));
+ if is_tonic {
+ extra_args.push(format!("--tonic_opt={}", tonic_or_prost_opt));
+ }
+ }
+
+ if protoc.is_none() {
+ return Err(
+ "No `--protoc` value was found. Unable to parse path to proto compiler."
+ .to_string(),
+ );
+ }
+ if out_dir.is_none() {
+ return Err(
+ "No `--prost_out` value was found. Unable to parse output directory.".to_string(),
+ );
+ }
+ if crate_name.is_none() {
+ return Err(
+ "No `--package_info_output` value was found. Unable to parse target crate name."
+ .to_string(),
+ );
+ }
+ if package_info_file.is_none() {
+ return Err("No `--package_info_output` value was found. Unable to parse package info output file.".to_string());
+ }
+ if out_librs.is_none() {
+ return Err("No `--out_librs` value was found. Unable to parse the output location for all combined prost outputs.".to_string());
+ }
+ if descriptor_set.is_none() {
+ return Err(
+ "No `--descriptor_set` value was found. Unable to parse descriptor set path."
+ .to_string(),
+ );
+ }
+ if label.is_none() {
+ return Err(
+ "No `--label` value was found. Unable to parse the label of the target crate."
+ .to_string(),
+ );
+ }
+
+ Ok(Args {
+ protoc: protoc.unwrap(),
+ out_dir: out_dir.unwrap(),
+ crate_name: crate_name.unwrap(),
+ package_info_file: package_info_file.unwrap(),
+ proto_files,
+ includes,
+ descriptor_set: descriptor_set.unwrap(),
+ out_librs: out_librs.unwrap(),
+ rustfmt,
+ proto_paths,
+ is_tonic,
+ label: label.unwrap(),
+ extra_args,
+ })
+ }
+}
+
+/// Get the output directory with the label suffixed.
+fn get_output_dir(out_dir: &Path, label: &str) -> PathBuf {
+ let label_as_path = label
+ .replace('@', "")
+ .replace("//", "_")
+ .replace(['/', ':'], "_");
+ PathBuf::from(format!(
+ "{}/prost-build-{}",
+ out_dir.display(),
+ label_as_path
+ ))
+}
+
+/// Get the output directory with the label suffixed, and create it if it doesn't exist.
+///
+/// This will remove the directory first if it already exists.
+fn get_and_create_output_dir(out_dir: &Path, label: &str) -> PathBuf {
+ let out_dir = get_output_dir(out_dir, label);
+ if out_dir.exists() {
+ fs::remove_dir_all(&out_dir).expect("Failed to remove old output directory");
+ }
+ fs::create_dir_all(&out_dir).expect("Failed to create output directory");
+ out_dir
+}
+
+/// Parse the descriptor set file into a `FileDescriptorSet`.
+fn parse_descriptor_set_file(descriptor_set_path: &PathBuf) -> FileDescriptorSet {
+ let descriptor_set_bytes =
+ fs::read(descriptor_set_path).expect("Failed to read descriptor set");
+ let descriptor_set = FileDescriptorSet::decode(descriptor_set_bytes.as_slice())
+ .expect("Failed to decode descriptor set");
+
+ descriptor_set
+}
+
+/// Get the package name from the descriptor set.
+fn get_package_name(descriptor_set: &FileDescriptorSet) -> Option<String> {
+ let mut package_name = None;
+
+ for file in &descriptor_set.file {
+ if let Some(package) = &file.package {
+ package_name = Some(package.clone());
+ break;
+ }
+ }
+
+ package_name
+}
+
+/// Whether the proto file should expect to generate a .rs file.
+///
+/// If the proto file contains any messages, enums, or services, then it should generate a rust file.
+/// If the proto file only contains extensions, then it will not generate any rust files.
+fn expect_fs_file_to_be_generated(descriptor_set: &FileDescriptorSet) -> bool {
+ let mut expect_rs = false;
+
+ for file in descriptor_set.file.iter() {
+ let has_messages = !file.message_type.is_empty();
+ let has_enums = !file.enum_type.is_empty();
+ let has_services = !file.service.is_empty();
+ let has_extensions = !file.extension.is_empty();
+
+ let has_definition = has_messages || has_enums || has_services;
+
+ if has_definition {
+ return true;
+ } else if !has_definition && !has_extensions {
+ expect_rs = true;
+ }
+ }
+
+ expect_rs
+}
+
+/// Whether the proto file should expect to generate service definitions.
+fn has_services(descriptor_set: &FileDescriptorSet) -> bool {
+ descriptor_set
+ .file
+ .iter()
+ .any(|file| !file.service.is_empty())
+}
+
+fn main() {
+ // Always enable backtraces for the protoc wrapper.
+ env::set_var("RUST_BACKTRACE", "1");
+
+ let Args {
+ protoc,
+ out_dir,
+ crate_name,
+ label,
+ package_info_file,
+ proto_files,
+ includes,
+ descriptor_set,
+ out_librs,
+ rustfmt,
+ proto_paths,
+ is_tonic,
+ extra_args,
+ } = Args::parse().expect("Failed to parse args");
+
+ let out_dir = get_and_create_output_dir(&out_dir, &label);
+
+ let descriptor_set = parse_descriptor_set_file(&descriptor_set);
+ let package_name = get_package_name(&descriptor_set).unwrap_or_default();
+ let expect_rs = expect_fs_file_to_be_generated(&descriptor_set);
+ let has_services = has_services(&descriptor_set);
+
+ if has_services && !is_tonic {
+ println!("Warning: Service definitions will not be generated because the prost toolchain did not define a tonic plugin.");
+ }
+
+ let mut cmd = process::Command::new(&protoc);
+ cmd.arg(format!("--prost_out={}", out_dir.display()));
+ if is_tonic {
+ cmd.arg(format!("--tonic_out={}", out_dir.display()));
+ }
+ cmd.args(extra_args);
+ cmd.args(
+ proto_paths
+ .iter()
+ .map(|proto_path| format!("--proto_path={}", proto_path)),
+ );
+ cmd.args(includes.iter().map(|include| format!("-I{}", include)));
+ cmd.args(&proto_files);
+
+ let status = cmd.status().expect("Failed to spawn protoc process");
+ if !status.success() {
+ panic!(
+ "protoc failed with status: {}",
+ status.code().expect("failed to get exit code")
+ );
+ }
+
+ // Not all proto files will consistently produce `.rs` or `.tonic.rs` files. This is
+ // caused by the proto file being transpiled not having an RPC service or other protos
+ // defined (a natural and expected situation). To guarantee consistent outputs, all
+ // `.rs` files are either renamed to `.tonic.rs` if there is no `.tonic.rs` or prepended
+ // to the existing `.tonic.rs`.
+ if is_tonic {
+ let tonic_files: BTreeSet<PathBuf> = find_generated_rust_files(&out_dir);
+
+ for tonic_file in tonic_files.iter() {
+ let tonic_path_str = tonic_file.to_str().expect("Failed to convert to str");
+ let filename = tonic_file
+ .file_name()
+ .expect("Failed to get file name")
+ .to_str()
+ .expect("Failed to convert to str");
+
+ let is_tonic_file = filename.ends_with(".tonic.rs");
+
+ if is_tonic_file {
+ let rs_file_str = format!(
+ "{}.rs",
+ tonic_path_str
+ .strip_suffix(".tonic.rs")
+ .expect("Failed to strip suffix.")
+ );
+ let rs_file = PathBuf::from(&rs_file_str);
+
+ if rs_file.exists() {
+ let rs_content = fs::read_to_string(&rs_file).expect("Failed to read file.");
+ let tonic_content =
+ fs::read_to_string(tonic_file).expect("Failed to read file.");
+ fs::write(tonic_file, format!("{}\n{}", rs_content, tonic_content))
+ .expect("Failed to write file.");
+ fs::remove_file(&rs_file).unwrap_or_else(|err| {
+ panic!("Failed to remove file: {err:?}: {rs_file:?}")
+ });
+ }
+ } else {
+ let real_tonic_file = PathBuf::from(format!(
+ "{}.tonic.rs",
+ tonic_path_str
+ .strip_suffix(".rs")
+ .expect("Failed to strip suffix.")
+ ));
+ if real_tonic_file.exists() {
+ continue;
+ }
+ fs::rename(tonic_file, &real_tonic_file).unwrap_or_else(|err| {
+ panic!("Failed to rename file: {err:?}: {tonic_file:?} -> {real_tonic_file:?}");
+ });
+ }
+ }
+ }
+
+ // Locate all prost-generated outputs.
+ let mut rust_files = find_generated_rust_files(&out_dir);
+ if rust_files.is_empty() {
+ if expect_rs {
+ panic!("No .rs files were generated by prost.");
+ } else {
+ let file_stem = if package_name.is_empty() {
+ "_"
+ } else {
+ &package_name
+ };
+ let file_stem = format!("{}{}", file_stem, if is_tonic { ".tonic" } else { "" });
+ let empty_rs_file = out_dir.join(format!("{}.rs", file_stem));
+ fs::write(&empty_rs_file, "").expect("Failed to write file.");
+ rust_files.insert(empty_rs_file);
+ }
+ }
+
+ let extern_paths = get_extern_paths(&descriptor_set, &crate_name)
+ .expect("Failed to compute proto package info");
+
+ // Write outputs
+ fs::write(&out_librs, generate_lib_rs(&rust_files, is_tonic)).expect("Failed to write file.");
+ fs::write(
+ package_info_file,
+ extern_paths
+ .into_iter()
+ .map(|(proto_path, rust_path)| format!(".{}=::{}", proto_path, rust_path))
+ .collect::<Vec<_>>()
+ .join("\n"),
+ )
+ .expect("Failed to write file.");
+
+ // Finally run rustfmt on the output lib.rs file
+ if let Some(rustfmt) = rustfmt {
+ let fmt_status = process::Command::new(rustfmt)
+ .arg("--edition")
+ .arg("2021")
+ .arg("--quiet")
+ .arg(&out_librs)
+ .status()
+ .expect("Failed to spawn rustfmt process");
+ if !fmt_status.success() {
+ panic!(
+ "rustfmt failed with exit code: {}",
+ fmt_status.code().expect("Failed to get exit code")
+ );
+ }
+ }
+}
+
+/// Rust built-in keywords and reserved keywords.
+const RUST_KEYWORDS: [&str; 51] = [
+ "abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "crate",
+ "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in",
+ "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
+ "return", "self", "Self", "static", "struct", "super", "trait", "true", "try", "type",
+ "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
+];
+
+/// Returns true if the given string is a Rust keyword.
+fn is_keyword(s: &str) -> bool {
+ RUST_KEYWORDS.contains(&s)
+}
+
+/// Escapes a Rust keyword by prefixing it with `r#`.
+fn escape_keyword(s: String) -> String {
+ if is_keyword(&s) {
+ return format!("r#{s}");
+ }
+ s
+}
+
+#[cfg(test)]
+mod test {
+
+ use super::*;
+
+ use prost_types::{FieldDescriptorProto, FileDescriptorProto, ServiceDescriptorProto};
+ use std::collections::BTreeMap;
+
+ #[test]
+ fn oneof_type_to_extern_paths_test() {
+ let oneof_descriptor = OneofDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..OneofDescriptorProto::default()
+ };
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ oneof_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar"),
+ &RustModulePath::from("bar"),
+ &oneof_descriptor,
+ );
+
+ assert_eq!(extern_paths.len(), 1);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.Foo")),
+ Some(&RustModulePath::from("bar::Foo"))
+ );
+ }
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ oneof_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar.baz"),
+ &RustModulePath::from("bar::baz"),
+ &oneof_descriptor,
+ );
+
+ assert_eq!(extern_paths.len(), 1);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.baz.Foo")),
+ Some(&RustModulePath::from("bar::baz::Foo"))
+ );
+ }
+ }
+
+ #[test]
+ fn enum_type_to_extern_paths_test() {
+ let enum_descriptor = EnumDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..EnumDescriptorProto::default()
+ };
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ enum_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar"),
+ &RustModulePath::from("bar"),
+ &enum_descriptor,
+ );
+
+ assert_eq!(extern_paths.len(), 1);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.Foo")),
+ Some(&RustModulePath::from("bar::Foo"))
+ );
+ }
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ enum_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar.baz"),
+ &RustModulePath::from("bar::baz"),
+ &enum_descriptor,
+ );
+
+ assert_eq!(extern_paths.len(), 1);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.baz.Foo")),
+ Some(&RustModulePath::from("bar::baz::Foo"))
+ );
+ }
+ }
+
+ #[test]
+ fn message_type_to_extern_paths_test() {
+ let message_descriptor = DescriptorProto {
+ name: Some("Foo".to_string()),
+ nested_type: vec![
+ DescriptorProto {
+ name: Some("Bar".to_string()),
+ ..DescriptorProto::default()
+ },
+ DescriptorProto {
+ name: Some("Nested".to_string()),
+ nested_type: vec![DescriptorProto {
+ name: Some("Baz".to_string()),
+ enum_type: vec![EnumDescriptorProto {
+ name: Some("Chuck".to_string()),
+ ..EnumDescriptorProto::default()
+ }],
+ ..DescriptorProto::default()
+ }],
+ ..DescriptorProto::default()
+ },
+ ],
+ enum_type: vec![EnumDescriptorProto {
+ name: Some("Qux".to_string()),
+ ..EnumDescriptorProto::default()
+ }],
+ ..DescriptorProto::default()
+ };
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ message_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar"),
+ &RustModulePath::from("bar"),
+ &message_descriptor,
+ );
+ assert_eq!(extern_paths.len(), 6);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.Foo")),
+ Some(&RustModulePath::from("bar::Foo"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.foo.Bar")),
+ Some(&RustModulePath::from("bar::foo::Bar"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.foo.Nested")),
+ Some(&RustModulePath::from("bar::foo::Nested"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.foo.nested.Baz")),
+ Some(&RustModulePath::from("bar::foo::nested::Baz"))
+ );
+ }
+
+ {
+ let mut extern_paths = BTreeMap::new();
+ message_type_to_extern_paths(
+ &mut extern_paths,
+ &ProtoPath::from("bar.bob"),
+ &RustModulePath::from("bar::bob"),
+ &message_descriptor,
+ );
+ assert_eq!(extern_paths.len(), 6);
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.bob.Foo")),
+ Some(&RustModulePath::from("bar::bob::Foo"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.bob.foo.Bar")),
+ Some(&RustModulePath::from("bar::bob::foo::Bar"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.bob.foo.Nested")),
+ Some(&RustModulePath::from("bar::bob::foo::Nested"))
+ );
+ assert_eq!(
+ extern_paths.get(&ProtoPath::from("bar.bob.foo.nested.Baz")),
+ Some(&RustModulePath::from("bar::bob::foo::nested::Baz"))
+ );
+ }
+ }
+
+ #[test]
+ fn proto_path_test() {
+ {
+ let proto_path = ProtoPath::from("");
+ assert_eq!(proto_path.to_string(), "");
+ assert_eq!(proto_path.join("foo"), ProtoPath::from("foo"));
+ }
+ {
+ let proto_path = ProtoPath::from("foo");
+ assert_eq!(proto_path.to_string(), "foo");
+ assert_eq!(proto_path.join(""), ProtoPath::from("foo"));
+ }
+ {
+ let proto_path = ProtoPath::from("foo");
+ assert_eq!(proto_path.to_string(), "foo");
+ assert_eq!(proto_path.join("bar"), ProtoPath::from("foo.bar"));
+ }
+ {
+ let proto_path = ProtoPath::from("foo.bar");
+ assert_eq!(proto_path.to_string(), "foo.bar");
+ assert_eq!(proto_path.join("baz"), ProtoPath::from("foo.bar.baz"));
+ }
+ {
+ let proto_path = ProtoPath::from("Foo.baR");
+ assert_eq!(proto_path.to_string(), "Foo.baR");
+ assert_eq!(proto_path.join("baz"), ProtoPath::from("Foo.baR.baz"));
+ }
+ }
+
+ #[test]
+ fn rust_module_path_test() {
+ {
+ let rust_module_path = RustModulePath::from("");
+ assert_eq!(rust_module_path.to_string(), "");
+ assert_eq!(rust_module_path.join("foo"), RustModulePath::from("foo"));
+ }
+ {
+ let rust_module_path = RustModulePath::from("foo");
+ assert_eq!(rust_module_path.to_string(), "foo");
+ assert_eq!(rust_module_path.join(""), RustModulePath::from("foo"));
+ }
+ {
+ let rust_module_path = RustModulePath::from("foo");
+ assert_eq!(rust_module_path.to_string(), "foo");
+ assert_eq!(
+ rust_module_path.join("bar"),
+ RustModulePath::from("foo::bar")
+ );
+ }
+ {
+ let rust_module_path = RustModulePath::from("foo::bar");
+ assert_eq!(rust_module_path.to_string(), "foo::bar");
+ assert_eq!(
+ rust_module_path.join("baz"),
+ RustModulePath::from("foo::bar::baz")
+ );
+ }
+ }
+
+ #[test]
+ fn expect_fs_file_to_be_generated_test() {
+ {
+ // Empty descriptor set should create a file.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(expect_fs_file_to_be_generated(&descriptor_set));
+ }
+ {
+ // Descriptor set with only message should create a file.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ message_type: vec![DescriptorProto {
+ name: Some("Foo".to_string()),
+ ..DescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(expect_fs_file_to_be_generated(&descriptor_set));
+ }
+ {
+ // Descriptor set with only enum should create a file.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ enum_type: vec![EnumDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..EnumDescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(expect_fs_file_to_be_generated(&descriptor_set));
+ }
+ {
+ // Descriptor set with only service should create a file.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ service: vec![ServiceDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..ServiceDescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(expect_fs_file_to_be_generated(&descriptor_set));
+ }
+ {
+ // Descriptor set with only extensions should not create a file.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ extension: vec![FieldDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..FieldDescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(!expect_fs_file_to_be_generated(&descriptor_set));
+ }
+ }
+
+ #[test]
+ fn has_services_test() {
+ {
+ // Empty file should not have services.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(!has_services(&descriptor_set));
+ }
+ {
+ // File with only message should not have services.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ message_type: vec![DescriptorProto {
+ name: Some("Foo".to_string()),
+ ..DescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(!has_services(&descriptor_set));
+ }
+ {
+ // File with services should have services.
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ service: vec![ServiceDescriptorProto {
+ name: Some("Foo".to_string()),
+ ..ServiceDescriptorProto::default()
+ }],
+ ..FileDescriptorProto::default()
+ }],
+ };
+ assert!(has_services(&descriptor_set));
+ }
+ }
+
+ #[test]
+ fn get_package_name_test() {
+ let descriptor_set = FileDescriptorSet {
+ file: vec![FileDescriptorProto {
+ name: Some("foo.proto".to_string()),
+ package: Some("foo".to_string()),
+ ..FileDescriptorProto::default()
+ }],
+ };
+
+ assert_eq!(get_package_name(&descriptor_set), Some("foo".to_string()));
+ }
+
+ #[test]
+ fn is_keyword_test() {
+ let non_keywords = [
+ "foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply", "waldo", "fred",
+ "plugh", "xyzzy", "thud",
+ ];
+ for non_keyword in &non_keywords {
+ assert!(!is_keyword(non_keyword));
+ }
+
+ for keyword in &RUST_KEYWORDS {
+ assert!(is_keyword(keyword));
+ }
+ }
+
+ #[test]
+ fn escape_keyword_test() {
+ let non_keywords = [
+ "foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply", "waldo", "fred",
+ "plugh", "xyzzy", "thud",
+ ];
+ for non_keyword in &non_keywords {
+ assert_eq!(
+ escape_keyword(non_keyword.to_string()),
+ non_keyword.to_owned()
+ );
+ }
+
+ for keyword in &RUST_KEYWORDS {
+ assert_eq!(
+ escape_keyword(keyword.to_string()),
+ format!("r#{}", keyword)
+ );
+ }
+ }
+}