diff --git a/docs/src/rust_prost.md b/docs/src/rust_prost.md
index f74039b576..b6b816bc61 100644
--- a/docs/src/rust_prost.md
+++ b/docs/src/rust_prost.md
@@ -192,3 +192,57 @@ Rust Prost toolchain rule.
| tonic_runtime | The Tonic runtime crates to use. | Label | optional | `None` |
+
+
+## rust_prost_transform
+
+
+rust_prost_transform(name, deps, srcs, prost_opts, tonic_opts)
+
+
+A rule for transforming the outputs of `ProstGenProto` actions.
+
+This rule is used by adding it to the `data` attribute of `proto_library` targets. E.g.
+```python
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust_prost//:defs.bzl", "rust_prost_library", "rust_prost_transform")
+
+rust_prost_transform(
+ name = "a_transform",
+ srcs = [
+ "a_src.rs",
+ ],
+)
+
+proto_library(
+ name = "a_proto",
+ srcs = [
+ "a.proto",
+ ],
+ data = [
+ ":transform",
+ ],
+)
+
+rust_prost_library(
+ name = "a_rs_proto",
+ proto = ":a_proto",
+)
+```
+
+The `rust_prost_library` will spawn an action on the `a_proto` target which consumes the
+`a_transform` rule to provide a means of granularly modifying a proto library for `ProstGenProto`
+actions with minimal impact to other consumers.
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| name | A unique name for this target. | Name | required | |
+| deps | Additional dependencies to add to the compiled crate. | List of labels | optional | `[]` |
+| srcs | Additional source files to include in generated Prost source code. | List of labels | optional | `[]` |
+| prost_opts | Additional options to add to Prost. | List of strings | optional | `[]` |
+| tonic_opts | Additional options to add to Tonic. | List of strings | optional | `[]` |
+
+
diff --git a/extensions/prost/defs.bzl b/extensions/prost/defs.bzl
index 6d3ddb7041..40b3af086c 100644
--- a/extensions/prost/defs.bzl
+++ b/extensions/prost/defs.bzl
@@ -146,6 +146,11 @@ load(
_rust_prost_library = "rust_prost_library",
_rust_prost_toolchain = "rust_prost_toolchain",
)
+load(
+ "//private:prost_transform.bzl",
+ _rust_prost_transform = "rust_prost_transform",
+)
rust_prost_library = _rust_prost_library
rust_prost_toolchain = _rust_prost_toolchain
+rust_prost_transform = _rust_prost_transform
diff --git a/extensions/prost/private/prost.bzl b/extensions/prost/private/prost.bzl
index 1a87d6c187..9f903dd001 100644
--- a/extensions/prost/private/prost.bzl
+++ b/extensions/prost/private/prost.bzl
@@ -19,6 +19,7 @@ load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action")
# buildifier: disable=bzl-visibility
load("@rules_rust//rust/private:utils.bzl", "can_build_metadata")
load("//:providers.bzl", "ProstProtoInfo")
+load(":prost_transform.bzl", "ProstTransformInfo")
RUST_EDITION = "2021"
@@ -39,7 +40,15 @@ def _create_proto_lang_toolchain(ctx, prost_toolchain):
return proto_lang_toolchain
-def _compile_proto(ctx, crate_name, proto_info, deps, prost_toolchain, rustfmt_toolchain = None):
+def _compile_proto(
+ *,
+ ctx,
+ crate_name,
+ proto_info,
+ transform_infos,
+ deps,
+ prost_toolchain,
+ rustfmt_toolchain = None):
deps_info_file = ctx.actions.declare_file(ctx.label.name + ".prost_deps_info")
dep_package_infos = [dep[ProstProtoInfo].package_info for dep in deps]
ctx.actions.write(
@@ -53,6 +62,15 @@ def _compile_proto(ctx, crate_name, proto_info, deps, prost_toolchain, rustfmt_t
proto_compiler = prost_toolchain.proto_compiler
tools = depset([proto_compiler.executable])
+ tonic_opts = []
+ prost_opts = []
+ additional_srcs = []
+ for transform_info in transform_infos:
+ tonic_opts.extend(transform_info.tonic_opts)
+ prost_opts.extend(transform_info.prost_opts)
+ additional_srcs.append(transform_info.srcs)
+
+ all_additional_srcs = depset(transitive = additional_srcs)
direct_crate_names = [dep[ProstProtoInfo].dep_variant_info.crate_info.name for dep in deps]
additional_args = ctx.actions.args()
@@ -65,7 +83,8 @@ def _compile_proto(ctx, crate_name, proto_info, deps, prost_toolchain, rustfmt_t
additional_args.add("--direct_dep_crate_names={}".format(",".join(direct_crate_names)))
additional_args.add("--prost_opt=compile_well_known_types")
additional_args.add("--descriptor_set={}".format(proto_info.direct_descriptor_set.path))
- additional_args.add_all(prost_toolchain.prost_opts, format_each = "--prost_opt=%s")
+ additional_args.add("--additional_srcs={}".format(",".join([f.path for f in all_additional_srcs.to_list()])))
+ additional_args.add_all(prost_toolchain.prost_opts + prost_opts, format_each = "--prost_opt=%s")
if prost_toolchain.tonic_plugin:
tonic_plugin = prost_toolchain.tonic_plugin[DefaultInfo].files_to_run
@@ -73,14 +92,18 @@ def _compile_proto(ctx, crate_name, proto_info, deps, prost_toolchain, rustfmt_t
additional_args.add("--tonic_opt=no_include")
additional_args.add("--tonic_opt=compile_well_known_types")
additional_args.add("--is_tonic")
- additional_args.add_all(prost_toolchain.tonic_opts, format_each = "--tonic_opt=%s")
+
+ additional_args.add_all(prost_toolchain.tonic_opts + tonic_opts, format_each = "--tonic_opt=%s")
tools = depset([tonic_plugin.executable], transitive = [tools])
if rustfmt_toolchain:
additional_args.add("--rustfmt={}".format(rustfmt_toolchain.rustfmt.path))
tools = depset(transitive = [tools, rustfmt_toolchain.all_files])
- additional_inputs = depset([deps_info_file, proto_info.direct_descriptor_set] + [dep[ProstProtoInfo].package_info for dep in deps])
+ additional_inputs = depset(
+ [deps_info_file, proto_info.direct_descriptor_set] + [dep[ProstProtoInfo].package_info for dep in deps],
+ transitive = [all_additional_srcs],
+ )
proto_common.compile(
actions = ctx.actions,
@@ -116,7 +139,14 @@ def _get_cc_info(providers):
return provider
fail("Couldn't find a CcInfo in the list of providers")
-def _compile_rust(ctx, attr, crate_name, src, deps, edition):
+def _compile_rust(
+ *,
+ ctx,
+ attr,
+ crate_name,
+ src,
+ deps,
+ edition):
"""Compiles a Rust source file.
Args:
@@ -233,7 +263,14 @@ def _rust_prost_aspect_impl(target, ctx):
if RustAnalyzerInfo in proto_dep:
rust_analyzer_deps.append(proto_dep[RustAnalyzerInfo])
- deps = runtime_deps + direct_deps
+ transform_infos = []
+ for data_target in getattr(ctx.rule.attr, "data", []):
+ if ProstTransformInfo in data_target:
+ transform_infos.append(data_target[ProstTransformInfo])
+
+ rust_deps = runtime_deps + direct_deps
+ for transform_info in transform_infos:
+ rust_deps.extend(transform_info.deps)
crate_name = ctx.label.name.replace("-", "_").replace("/", "_")
@@ -243,6 +280,7 @@ def _rust_prost_aspect_impl(target, ctx):
ctx = ctx,
crate_name = crate_name,
proto_info = proto_info,
+ transform_infos = transform_infos,
deps = proto_deps,
prost_toolchain = prost_toolchain,
rustfmt_toolchain = rustfmt_toolchain,
@@ -253,7 +291,7 @@ def _rust_prost_aspect_impl(target, ctx):
attr = ctx.rule.attr,
crate_name = crate_name,
src = lib_rs,
- deps = deps,
+ deps = rust_deps,
edition = RUST_EDITION,
)
@@ -495,7 +533,7 @@ def _current_prost_runtime_impl(ctx):
)]
current_prost_runtime = rule(
- doc = "A rule for accessing the current Prost toolchain components needed by the process wrapper",
+ doc = "A rule for accessing the current Prost toolchain components needed by the process wrapper.",
provides = [rust_common.crate_group_info],
implementation = _current_prost_runtime_impl,
toolchains = [TOOLCHAIN_TYPE],
diff --git a/extensions/prost/private/prost_transform.bzl b/extensions/prost/private/prost_transform.bzl
new file mode 100644
index 0000000000..1712a49e86
--- /dev/null
+++ b/extensions/prost/private/prost_transform.bzl
@@ -0,0 +1,88 @@
+"""Prost rules."""
+
+load("@rules_rust//rust:defs.bzl", "rust_common")
+
+ProstTransformInfo = provider(
+ doc = "Info about transformations to apply to Prost generated source code.",
+ fields = {
+ "deps": "List[DepVariantInfo]: Additional dependencies to compile into the Prost target.",
+ "prost_opts": "List[str]: Additional prost flags.",
+ "srcs": "Depset[File]: Additional source files to include in generated Prost source code.",
+ "tonic_opts": "List[str]: Additional tonic flags.",
+ },
+)
+
+def _rust_prost_transform_impl(ctx):
+ deps = []
+ for target in ctx.attr.deps:
+ deps.append(rust_common.dep_variant_info(
+ crate_info = target[rust_common.crate_info] if rust_common.crate_info in target else None,
+ dep_info = target[rust_common.dep_info] if rust_common.dep_info in target else None,
+ cc_info = target[CcInfo] if CcInfo in target else None,
+ build_info = None,
+ ))
+
+ # DefaultInfo is intentionally not returned here to avoid impacting other
+ # consumers of the `proto_library` target this rule is expected to be passed
+ # to.
+ return [ProstTransformInfo(
+ deps = deps,
+ prost_opts = ctx.attr.prost_opts,
+ srcs = depset(ctx.files.srcs),
+ tonic_opts = ctx.attr.tonic_opts,
+ )]
+
+rust_prost_transform = rule(
+ doc = """\
+A rule for transforming the outputs of `ProstGenProto` actions.
+
+This rule is used by adding it to the `data` attribute of `proto_library` targets. E.g.
+```python
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust_prost//:defs.bzl", "rust_prost_library", "rust_prost_transform")
+
+rust_prost_transform(
+ name = "a_transform",
+ srcs = [
+ "a_src.rs",
+ ],
+)
+
+proto_library(
+ name = "a_proto",
+ srcs = [
+ "a.proto",
+ ],
+ data = [
+ ":transform",
+ ],
+)
+
+rust_prost_library(
+ name = "a_rs_proto",
+ proto = ":a_proto",
+)
+```
+
+The `rust_prost_library` will spawn an action on the `a_proto` target which consumes the
+`a_transform` rule to provide a means of granularly modifying a proto library for `ProstGenProto`
+actions with minimal impact to other consumers.
+""",
+ implementation = _rust_prost_transform_impl,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "Additional dependencies to add to the compiled crate.",
+ providers = [[rust_common.crate_info], [rust_common.crate_group_info]],
+ ),
+ "prost_opts": attr.string_list(
+ doc = "Additional options to add to Prost.",
+ ),
+ "srcs": attr.label_list(
+ doc = "Additional source files to include in generated Prost source code.",
+ allow_files = True,
+ ),
+ "tonic_opts": attr.string_list(
+ doc = "Additional options to add to Tonic.",
+ ),
+ },
+)
diff --git a/extensions/prost/private/protoc_wrapper.rs b/extensions/prost/private/protoc_wrapper.rs
index 0facb982b8..a0403f82e8 100644
--- a/extensions/prost/private/protoc_wrapper.rs
+++ b/extensions/prost/private/protoc_wrapper.rs
@@ -102,6 +102,9 @@ impl Module {
}
}
+const ADDITIONAL_CONTENT_HEADER: &str =
+ "// A D D I T I O N A L S O U R C E S ========================================";
+
/// 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.
@@ -152,6 +155,7 @@ fn generate_lib_rs(
prost_outputs: &BTreeSet,
is_tonic: bool,
direct_dep_crate_names: Vec,
+ additional_content: String,
) -> String {
let mut contents = vec!["// @generated".to_string(), "".to_string()];
for crate_name in direct_dep_crate_names {
@@ -193,6 +197,14 @@ fn generate_lib_rs(
let mut content = String::new();
write_module(&mut content, &module_info, 0);
+
+ if !additional_content.is_empty() {
+ return format!(
+ "{}\n\n{}\n\n{}",
+ content, ADDITIONAL_CONTENT_HEADER, additional_content
+ );
+ }
+
content
}
@@ -421,6 +433,9 @@ struct Args {
/// The proto files to compile.
proto_files: Vec,
+ /// Additional source files to append to the generated rust source.
+ additional_srcs: Vec,
+
/// The include directories.
includes: Vec,
@@ -454,6 +469,7 @@ impl Args {
let mut crate_name: Option = None;
let mut package_info_file: Option = None;
let mut proto_files: Vec = Vec::new();
+ let mut additional_srcs: Vec = Vec::new();
let mut includes = Vec::new();
let mut descriptor_set = None;
let mut out_librs: Option = None;
@@ -521,6 +537,12 @@ impl Args {
}
}
}
+ ("--additional_srcs", value) => {
+ if !value.is_empty() {
+ additional_srcs
+ .extend(value.split(',').map(PathBuf::from).collect::>());
+ }
+ }
("--direct_dep_crate_names", value) => {
if value.trim().is_empty() {
return;
@@ -614,6 +636,7 @@ impl Args {
crate_name: crate_name.unwrap(),
package_info_file: package_info_file.unwrap(),
proto_files,
+ additional_srcs,
includes,
descriptor_set: descriptor_set.unwrap(),
out_librs: out_librs.unwrap(),
@@ -717,6 +740,7 @@ fn main() {
label,
package_info_file,
proto_files,
+ additional_srcs,
includes,
descriptor_set,
out_librs,
@@ -733,6 +757,19 @@ fn main() {
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);
+ let additional_content = additional_srcs
+ .into_iter()
+ .map(|f| {
+ fs::read_to_string(&f).unwrap_or_else(|e| {
+ panic!(
+ "Failed to read additional source file: `{}`\n{:?}",
+ f.display(),
+ e
+ )
+ })
+ })
+ .collect::>()
+ .join("\n");
if has_services && !is_tonic {
eprintln!("Warning: Service definitions will not be generated because the prost toolchain did not define a tonic plugin.");
@@ -875,7 +912,12 @@ fn main() {
// Write outputs
fs::write(
&out_librs,
- generate_lib_rs(&rust_files, is_tonic, direct_dep_crate_names),
+ generate_lib_rs(
+ &rust_files,
+ is_tonic,
+ direct_dep_crate_names,
+ additional_content,
+ ),
)
.expect("Failed to write file.");
fs::write(
diff --git a/extensions/prost/private/tests/transform/BUILD.bazel b/extensions/prost/private/tests/transform/BUILD.bazel
new file mode 100644
index 0000000000..b7b0dce435
--- /dev/null
+++ b/extensions/prost/private/tests/transform/BUILD.bazel
@@ -0,0 +1,43 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust//rust:defs.bzl", "rust_test")
+load("//:defs.bzl", "rust_prost_library", "rust_prost_transform")
+
+package(default_visibility = ["//private/tests:__subpackages__"])
+
+rust_prost_transform(
+ name = "transform",
+ srcs = ["a_src.rs"],
+)
+
+proto_library(
+ name = "a_proto",
+ srcs = [
+ "a.proto",
+ ],
+ data = [
+ ":transform",
+ ],
+ strip_import_prefix = "/private/tests/transform",
+ deps = [
+ "//private/tests/transform/b:b_proto",
+ "//private/tests/types:types_proto",
+ "@com_google_protobuf//:duration_proto",
+ "@com_google_protobuf//:timestamp_proto",
+ ],
+)
+
+rust_prost_library(
+ name = "a_rs_proto",
+ proto = ":a_proto",
+)
+
+rust_test(
+ name = "a_test",
+ srcs = ["a_test.rs"],
+ edition = "2021",
+ deps = [
+ ":a_rs_proto",
+ # Add b_proto as a dependency directly to ensure compatibility with `a.proto`'s imports.
+ "//private/tests/transform/b:b_rs_proto",
+ ],
+)
diff --git a/extensions/prost/private/tests/transform/a.proto b/extensions/prost/private/tests/transform/a.proto
new file mode 100644
index 0000000000..b619ef1a58
--- /dev/null
+++ b/extensions/prost/private/tests/transform/a.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/duration.proto";
+import "b/b.proto";
+import "types/types.proto";
+
+package a;
+
+message A {
+ string name = 1;
+
+ a.b.B b = 2;
+
+ google.protobuf.Timestamp timestamp = 3;
+
+ google.protobuf.Duration duration = 4;
+
+ Types types = 5;
+}
diff --git a/extensions/prost/private/tests/transform/a_src.rs b/extensions/prost/private/tests/transform/a_src.rs
new file mode 100644
index 0000000000..857d9491af
--- /dev/null
+++ b/extensions/prost/private/tests/transform/a_src.rs
@@ -0,0 +1,9 @@
+// Additional source code for `a.proto`.
+
+use std::fmt::{Display, Formatter, Result};
+
+impl Display for crate::a::A {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+ write!(f, "Display of: A")
+ }
+}
diff --git a/extensions/prost/private/tests/transform/a_test.rs b/extensions/prost/private/tests/transform/a_test.rs
new file mode 100644
index 0000000000..3e2ef5c3bc
--- /dev/null
+++ b/extensions/prost/private/tests/transform/a_test.rs
@@ -0,0 +1,55 @@
+//! Tests transitive dependencies.
+
+use a_proto::a::A;
+use a_proto::b_proto::c_proto::a::b::c::C;
+use a_proto::duration_proto::google::protobuf::Duration;
+use a_proto::timestamp_proto::google::protobuf::Timestamp;
+use a_proto::types_proto::Types;
+
+#[test]
+fn test_a() {
+ let duration = Duration {
+ seconds: 1,
+ nanos: 2,
+ };
+
+ let a = A {
+ name: "a".to_string(),
+ // Ensure the external `b_proto` dependency is compatible with `a_proto`'s `B`.
+ b: Some(b_proto::a::b::B {
+ name: "b".to_string(),
+ c: Some(C {
+ name: "c".to_string(),
+ }),
+ ..Default::default()
+ }),
+ timestamp: Some(Timestamp {
+ seconds: 1,
+ nanos: 2,
+ }),
+ duration: Some(duration),
+ types: Some(Types::default()),
+ };
+
+ assert_eq!(
+ "Display of: A",
+ format!("{}", a),
+ "Unexpected `Display` implementation for {:#?}",
+ a
+ );
+}
+
+#[test]
+fn test_b() {
+ use b_proto::Greeting;
+
+ let b = b_proto::a::b::B {
+ name: "b".to_string(),
+ c: Some(C {
+ name: "c".to_string(),
+ }),
+ ..Default::default()
+ };
+
+ assert_eq!("Hallo, Bazel, my name is B!", b.greet("Bazel"));
+}
diff --git a/extensions/prost/private/tests/transform/b/BUILD.bazel b/extensions/prost/private/tests/transform/b/BUILD.bazel
new file mode 100644
index 0000000000..73c46e499f
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/BUILD.bazel
@@ -0,0 +1,47 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+load("//:defs.bzl", "rust_prost_library", "rust_prost_transform")
+
+package(default_visibility = ["//private/tests:__subpackages__"])
+
+rust_library(
+ name = "greeting",
+ srcs = ["greeting.rs"],
+ edition = "2021",
+)
+
+rust_prost_transform(
+ name = "transform",
+ srcs = ["b_src.rs"],
+ deps = [":greeting"],
+)
+
+proto_library(
+ name = "b_proto",
+ srcs = [
+ "b.proto",
+ ],
+ data = [
+ ":transform",
+ ],
+ strip_import_prefix = "/private/tests/transform",
+ deps = [
+ "//private/tests/transform/b/c:c_proto",
+ "@com_google_protobuf//:empty_proto",
+ ],
+)
+
+rust_prost_library(
+ name = "b_rs_proto",
+ proto = ":b_proto",
+)
+
+rust_test(
+ name = "b_test",
+ srcs = ["b_test.rs"],
+ edition = "2021",
+ deps = [
+ ":b_rs_proto",
+ ":greeting",
+ ],
+)
diff --git a/extensions/prost/private/tests/transform/b/b.proto b/extensions/prost/private/tests/transform/b/b.proto
new file mode 100644
index 0000000000..f28db6a735
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/b.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+import "google/protobuf/empty.proto";
+import "b/c/c.proto";
+
+package a.b;
+
+message B {
+ string name = 1;
+
+ google.protobuf.Empty empty = 2;
+
+ a.b.c.C c = 3;
+}
diff --git a/extensions/prost/private/tests/transform/b/b_src.rs b/extensions/prost/private/tests/transform/b/b_src.rs
new file mode 100644
index 0000000000..63e11c4bd9
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/b_src.rs
@@ -0,0 +1,9 @@
+// Additional source code for `b.proto`.
+
+pub use greeting::Greeting;
+
+impl Greeting for crate::a::b::B {
+ fn greet(&self, name: &str) -> String {
+ format!("Hallo, {}, my name is B!", name)
+ }
+}
diff --git a/extensions/prost/private/tests/transform/b/b_test.rs b/extensions/prost/private/tests/transform/b/b_test.rs
new file mode 100644
index 0000000000..d7a1ea489d
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/b_test.rs
@@ -0,0 +1,19 @@
+//! Tests transitive dependencies.
+
+use b_proto::a::b::B;
+use b_proto::c_proto::a::b::c::C;
+
+use greeting::Greeting;
+
+#[test]
+fn test_b() {
+ let b = B {
+ name: "b".to_string(),
+ c: Some(C {
+ name: "c".to_string(),
+ }),
+ ..Default::default()
+ };
+
+ assert_eq!("Hallo, Bazel, my name is B!", b.greet("Bazel"));
+}
diff --git a/extensions/prost/private/tests/transform/b/c/BUILD.bazel b/extensions/prost/private/tests/transform/b/c/BUILD.bazel
new file mode 100644
index 0000000000..f2441c7444
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/c/BUILD.bazel
@@ -0,0 +1,49 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_rust//rust:defs.bzl", "rust_test")
+load("//:defs.bzl", "rust_prost_library", "rust_prost_transform")
+
+package(default_visibility = ["//private/tests:__subpackages__"])
+
+rust_prost_transform(
+ name = "transform_1",
+ prost_opts = [
+ "type_attribute=.=#[derive(Hash)]",
+ ],
+)
+
+rust_prost_transform(
+ name = "transform_2",
+ prost_opts = [
+ "type_attribute=.=#[derive(Eq)]",
+ ],
+)
+
+proto_library(
+ name = "c_proto",
+ srcs = [
+ "c.proto",
+ ],
+ data = [
+ ":transform_1",
+ ":transform_2",
+ ],
+ strip_import_prefix = "/private/tests/transform",
+ deps = [
+ "@com_google_protobuf//:any_proto",
+ "@com_google_protobuf//:duration_proto",
+ ],
+)
+
+rust_prost_library(
+ name = "c_rs_proto",
+ proto = ":c_proto",
+)
+
+rust_test(
+ name = "c_test",
+ srcs = ["c_test.rs"],
+ edition = "2021",
+ deps = [
+ ":c_rs_proto",
+ ],
+)
diff --git a/extensions/prost/private/tests/transform/b/c/c.proto b/extensions/prost/private/tests/transform/b/c/c.proto
new file mode 100644
index 0000000000..a515585486
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/c/c.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+
+package a.b.c;
+
+message C {
+ string name = 1;
+}
diff --git a/extensions/prost/private/tests/transform/b/c/c_test.rs b/extensions/prost/private/tests/transform/b/c/c_test.rs
new file mode 100644
index 0000000000..15a44c87dd
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/c/c_test.rs
@@ -0,0 +1,16 @@
+//! Tests transitive dependencies.
+
+use std::collections::HashSet;
+
+use c_proto::a::b::c::C;
+
+#[test]
+fn test_c() {
+ let c = C {
+ name: "c".to_string(),
+ };
+
+ // This shows that Hash and Eq are implemented for C
+ let set = HashSet::from([c]);
+ assert_eq!(set.len(), 1, "{:#?}", set);
+}
diff --git a/extensions/prost/private/tests/transform/b/greeting.rs b/extensions/prost/private/tests/transform/b/greeting.rs
new file mode 100644
index 0000000000..5ebfe16233
--- /dev/null
+++ b/extensions/prost/private/tests/transform/b/greeting.rs
@@ -0,0 +1,5 @@
+//! Implement a trait which is used to generate greetings.
+
+pub trait Greeting {
+ fn greet(&self, name: &str) -> String;
+}