Consolidate rust_prost_library and fix extension-only proto generation. (#2047)

This PR consolidates rust_prost_library and rust_tonic_library into one rule; `rust_prost_library`. `rust_prost_library` will generate tonic services if you provide a `tonic_plugin` in the toolchain definition. If you do not provide that plugin and you try to build a proto with services, it will print a warning that you should add a `tonic_plugin`.

This PR also handles extension-only proto files. Prost does not generate a file if there are no messages, enums, or services and it appears that Prost doesn't even support proto2 extensions. So to work around this issue, protoc_wrapper will generate an empty `.rs` file in the case that there are only extensions defined in a file.

Closes: #2046 
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index fe4399d..c412e2f 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -52,6 +52,10 @@
 
 rules_rust_test_deps()
 
+load("//test:deps_transitive.bzl", "rules_rust_test_deps_transitive")
+
+rules_rust_test_deps_transitive()
+
 # --- end stardoc
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
index f97a034..5408d9a 100644
--- a/docs/BUILD.bazel
+++ b/docs/BUILD.bazel
@@ -122,7 +122,6 @@
             "rust_proto_transitive_repositories",
             "rust_proto_toolchain",
             "rust_prost_library",
-            "rust_tonic_library",
         ],
     ),
     page(
diff --git a/docs/flatten.md b/docs/flatten.md
index 9997b80..08ed04a 100644
--- a/docs/flatten.md
+++ b/docs/flatten.md
@@ -44,7 +44,6 @@
 * [rust_stdlib_filegroup](#rust_stdlib_filegroup)
 * [rust_test](#rust_test)
 * [rust_test_suite](#rust_test_suite)
-* [rust_tonic_library](#rust_tonic_library)
 * [rust_toolchain](#rust_toolchain)
 * [rust_toolchain_repository](#rust_toolchain_repository)
 * [rust_toolchain_repository_proxy](#rust_toolchain_repository_proxy)
@@ -2017,25 +2016,6 @@
 | <a id="rust_test_suite-kwargs"></a>kwargs |  Additional keyword arguments for the underyling [rust_test](#rust_test) targets. The <code>tags</code> argument is also passed to the generated <code>test_suite</code> target.   |  none |
 
 
-<a id="rust_tonic_library"></a>
-
-## rust_tonic_library
-
-<pre>
-rust_tonic_library(<a href="#rust_tonic_library-name">name</a>, <a href="#rust_tonic_library-kwargs">kwargs</a>)
-</pre>
-
-A rule for generating a Rust library using Prost and Tonic.
-
-**PARAMETERS**
-
-
-| Name  | Description | Default Value |
-| :------------- | :------------- | :------------- |
-| <a id="rust_tonic_library-name"></a>name |  The name of the target.   |  none |
-| <a id="rust_tonic_library-kwargs"></a>kwargs |  Additional keyword arguments for the underlying <code>rust_tonic_library</code> rule.   |  none |
-
-
 <a id="rust_toolchain_repository"></a>
 
 ## rust_toolchain_repository
diff --git a/docs/rust_proto.md b/docs/rust_proto.md
index 1e0b041..5453e46 100644
--- a/docs/rust_proto.md
+++ b/docs/rust_proto.md
@@ -7,7 +7,6 @@
 * [rust_proto_transitive_repositories](#rust_proto_transitive_repositories)
 * [rust_proto_toolchain](#rust_proto_toolchain)
 * [rust_prost_library](#rust_prost_library)
-* [rust_tonic_library](#rust_tonic_library)
 
 
 ## Overview
@@ -15,8 +14,7 @@
 
 There are two rule sets. The first ruleset defines the `rust_proto_library` and `rust_grpc_library`
 rules which generate Rust code using the [`rust-protobuf`] dependencies. The second ruleset defines
-the `rust_prost_library` and `rust_tonic_library` rules which generate Rust code using the [`prost`]
-and [`tonic`] dependencies respectively.
+the `rust_prost_library` which generates Rust code using the [`prost`] and [`tonic`] dependencies.
 
 [rust]: http://www.rust-lang.org/
 [protobuf]: https://developers.google.com/protocol-buffers/
@@ -427,22 +425,3 @@
 
 
 
-<a id="rust_tonic_library"></a>
-
-## rust_tonic_library
-
-<pre>
-rust_tonic_library(<a href="#rust_tonic_library-name">name</a>, <a href="#rust_tonic_library-kwargs">kwargs</a>)
-</pre>
-
-A rule for generating a Rust library using Prost and Tonic.
-
-**PARAMETERS**
-
-
-| Name  | Description | Default Value |
-| :------------- | :------------- | :------------- |
-| <a id="rust_tonic_library-name"></a>name |  The name of the target.   |  none |
-| <a id="rust_tonic_library-kwargs"></a>kwargs |  Additional keyword arguments for the underlying <code>rust_tonic_library</code> rule.   |  none |
-
-
diff --git a/docs/rust_proto.vm b/docs/rust_proto.vm
index b2dce93..967b2ae 100644
--- a/docs/rust_proto.vm
+++ b/docs/rust_proto.vm
@@ -4,8 +4,7 @@
 
 There are two rule sets. The first ruleset defines the `rust_proto_library` and `rust_grpc_library`
 rules which generate Rust code using the [`rust-protobuf`] dependencies. The second ruleset defines
-the `rust_prost_library` and `rust_tonic_library` rules which generate Rust code using the [`prost`]
-and [`tonic`] dependencies respectively.
+the `rust_prost_library` which generates Rust code using the [`prost`] and [`tonic`] dependencies.
 
 [rust]: http://www.rust-lang.org/
 [protobuf]: https://developers.google.com/protocol-buffers/
diff --git a/docs/symbols.bzl b/docs/symbols.bzl
index d1a8d65..141dc60 100644
--- a/docs/symbols.bzl
+++ b/docs/symbols.bzl
@@ -32,7 +32,6 @@
     _rust_grpc_library = "rust_grpc_library",
     _rust_prost_library = "rust_prost_library",
     _rust_proto_library = "rust_proto_library",
-    _rust_tonic_library = "rust_tonic_library",
 )
 load(
     "@rules_rust//proto:repositories.bzl",
@@ -127,7 +126,6 @@
 rust_proto_library = _rust_proto_library
 rust_grpc_library = _rust_grpc_library
 rust_prost_library = _rust_prost_library
-rust_tonic_library = _rust_tonic_library
 
 rust_bindgen = _rust_bindgen
 rust_bindgen_dependencies = _rust_bindgen_dependencies
diff --git a/proto/defs.bzl b/proto/defs.bzl
index cc38b48..12d4bd3 100644
--- a/proto/defs.bzl
+++ b/proto/defs.bzl
@@ -3,7 +3,6 @@
 load(
     "//proto/prost:defs.bzl",
     _rust_prost_library = "rust_prost_library",
-    _rust_tonic_library = "rust_tonic_library",
 )
 load(
     ":proto.bzl",
@@ -15,4 +14,3 @@
 rust_grpc_library = _rust_grpc_library
 
 rust_prost_library = _rust_prost_library
-rust_tonic_library = _rust_tonic_library
diff --git a/proto/prost/defs.bzl b/proto/prost/defs.bzl
index 56a45c5..401f106 100644
--- a/proto/prost/defs.bzl
+++ b/proto/prost/defs.bzl
@@ -4,7 +4,6 @@
     "//proto/prost/private:prost.bzl",
     _rust_prost_library = "rust_prost_library",
     _rust_prost_toolchain = "rust_prost_toolchain",
-    _rust_tonic_library = "rust_tonic_library",
 )
 
 def rust_prost_library(name, **kwargs):
@@ -30,27 +29,4 @@
         **kwargs
     )
 
-def rust_tonic_library(name, **kwargs):
-    """A rule for generating a Rust library using Prost and Tonic.
-
-    Args:
-        name (str): The name of the target.
-        **kwargs (dict): Additional keyword arguments for the underlying
-            `rust_tonic_library` rule.
-    """
-
-    # Clippy and Rustfmt will attempt to run on these targets.
-    # This is not correct and likely a bug in target detection.
-    tags = kwargs.pop("tags", [])
-    if "no-clippy" not in tags:
-        tags.append("no-clippy")
-    if "no-rustfmt" not in tags:
-        tags.append("no-rustfmt")
-
-    _rust_tonic_library(
-        name = name,
-        tags = tags,
-        **kwargs
-    )
-
 rust_prost_toolchain = _rust_prost_toolchain
diff --git a/proto/prost/private/prost.bzl b/proto/prost/private/prost.bzl
index f41e4fe..e2b6c39 100644
--- a/proto/prost/private/prost.bzl
+++ b/proto/prost/private/prost.bzl
@@ -8,16 +8,22 @@
 
 # buildifier: disable=bzl-visibility
 load("//rust/private:utils.bzl", "can_build_metadata")
-load(":providers.bzl", "ProstProtoInfo", "TonicProtoInfo")
 
 RUST_EDITION = "2021"
 
 TOOLCHAIN_TYPE = "@rules_rust//proto/prost:toolchain_type"
 
-def _create_proto_lang_toolchain(ctx, prost_toolchain):
-    is_tonic = prost_toolchain.tonic_runtime != None
+ProstProtoInfo = provider(
+    doc = "Rust Prost provider info",
+    fields = {
+        "dep_variant_info": "DepVariantInfo: For the compiled Rust gencode (also covers its " +
+                            "transitive dependencies)",
+        "package_info": "File: A newline delimited file of `--extern_path` values for protoc.",
+        "transitive_dep_infos": "depset[DepVariantInfo]: Transitive dependencies of the compiled crate.",
+    },
+)
 
-    mnemonic = "TonicGenProto" if is_tonic else "ProstGenProto"
+def _create_proto_lang_toolchain(ctx, prost_toolchain):
     proto_lang_toolchain = proto_common.ProtoLangToolchainInfo(
         out_replacement_format_flag = "--prost_out=%s",
         plugin_format_flag = prost_toolchain.prost_plugin_flag,
@@ -26,26 +32,22 @@
         provided_proto_sources = depset(),
         proto_compiler = ctx.attr._prost_process_wrapper[DefaultInfo].files_to_run,
         protoc_opts = prost_toolchain.protoc_opts,
-        progress_message = mnemonic + " %{label}",
-        mnemonic = mnemonic,
+        progress_message = "ProstGenProto %{label}",
+        mnemonic = "ProstGenProto",
     )
 
     return proto_lang_toolchain
 
-def _compile_proto(ctx, crate_name, proto_info, deps, prost_toolchain, is_tonic, rustfmt_toolchain = None):
-    kind = "tonic" if is_tonic else "prost"
-    extension = ".tonic.rs" if is_tonic else ".rs"
-    provider = TonicProtoInfo if is_tonic else ProstProtoInfo
-
-    deps_info_file = ctx.actions.declare_file(ctx.label.name + ".{}_deps_info".format(kind))
-    dep_package_infos = [dep[provider].package_info for dep in deps]
+def _compile_proto(ctx, crate_name, proto_info, 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(
         output = deps_info_file,
         content = "\n".join([file.path for file in dep_package_infos]),
     )
 
-    package_info_file = ctx.actions.declare_file(ctx.label.name + ".{}_package_info".format(kind))
-    lib_rs = ctx.actions.declare_file("{}.lib{}".format(ctx.label.name, extension))
+    package_info_file = ctx.actions.declare_file(ctx.label.name + ".prost_package_info")
+    lib_rs = ctx.actions.declare_file("{}.lib.rs".format(ctx.label.name))
 
     proto_compiler = prost_toolchain.proto_compiler[DefaultInfo].files_to_run
     tools = depset([proto_compiler.executable])
@@ -62,10 +64,7 @@
     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")
 
-    if is_tonic:
-        if not prost_toolchain.tonic_plugin:
-            fail("Tonic plugin not configured for this toolchain")
-
+    if prost_toolchain.tonic_plugin:
         tonic_plugin = prost_toolchain.tonic_plugin[DefaultInfo].files_to_run
         additional_args.add(prost_toolchain.tonic_plugin_flag % tonic_plugin.executable.path)
         additional_args.add("--tonic_opt=no_include")
@@ -78,7 +77,7 @@
         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[provider].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])
 
     proto_common.compile(
         actions = ctx.actions,
@@ -114,7 +113,7 @@
             return provider
     fail("Couldn't find a CcInfo in the list of providers")
 
-def _compile_rust(ctx, attr, crate_name, src, deps, edition, is_tonic):
+def _compile_rust(ctx, attr, crate_name, src, deps, edition):
     """Compiles a Rust source file.
 
     Args:
@@ -124,13 +123,12 @@
       src (File): The crate root source file to be compiled.
       deps (List of DepVariantInfo): A list of dependencies needed.
       edition (str): The Rust edition to use.
-      is_tonic (bool): Whether or not the crate is a tonic library.
 
     Returns:
       A DepVariantInfo provider.
     """
     toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
-    output_hash = repr(hash(src.path + (".tonic" if is_tonic else ".prost")))
+    output_hash = repr(hash(src.path + ".prost"))
 
     lib_name = "{prefix}{name}-{lib_hash}{extension}".format(
         prefix = "lib",
@@ -194,8 +192,7 @@
     )
 
 def _rust_prost_aspect_impl(target, ctx):
-    proto_info_provider = TonicProtoInfo if ctx.attr._is_tonic else ProstProtoInfo
-    if proto_info_provider in target:
+    if ProstProtoInfo in target:
         return []
 
     runtime_deps = []
@@ -221,7 +218,7 @@
     direct_deps = []
     transitive_deps = []
     for proto_dep in proto_deps:
-        proto_info = proto_dep[proto_info_provider]
+        proto_info = proto_dep[ProstProtoInfo]
 
         direct_deps.append(proto_info.dep_variant_info)
         transitive_deps.append(depset(
@@ -241,7 +238,6 @@
         proto_info = proto_info,
         deps = proto_deps,
         prost_toolchain = prost_toolchain,
-        is_tonic = ctx.attr._is_tonic,
         rustfmt_toolchain = rustfmt_toolchain,
     )
 
@@ -252,100 +248,82 @@
         src = lib_rs,
         deps = deps,
         edition = RUST_EDITION,
-        is_tonic = ctx.attr._is_tonic,
     )
 
     return [
-        proto_info_provider(
+        ProstProtoInfo(
             dep_variant_info = dep_variant_info,
             transitive_dep_infos = depset(transitive = transitive_deps),
             package_info = package_info_file,
         ),
     ]
 
-def _make_rust_prost_aspect(doc, is_tonic):
-    return aspect(
-        doc = doc,
-        implementation = _rust_prost_aspect_impl,
-        attr_aspects = ["deps"],
-        attrs = {
-            "_cc_toolchain": attr.label(
-                doc = (
-                    "In order to use find_cc_toolchain, your rule has to depend " +
-                    "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
-                    "docs for details."
-                ),
-                default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
-            ),
-            "_collect_cc_coverage": attr.label(
-                default = Label("//util:collect_coverage"),
-                executable = True,
-                cfg = "exec",
-            ),
-            "_error_format": attr.label(
-                default = Label("//:error_format"),
-            ),
-            "_extra_exec_rustc_flag": attr.label(
-                default = Label("//:extra_exec_rustc_flag"),
-            ),
-            "_extra_exec_rustc_flags": attr.label(
-                default = Label("//:extra_exec_rustc_flags"),
-            ),
-            "_extra_rustc_flag": attr.label(
-                default = Label("//:extra_rustc_flag"),
-            ),
-            "_extra_rustc_flags": attr.label(
-                default = Label("//:extra_rustc_flags"),
-            ),
-            "_grep_includes": attr.label(
-                allow_single_file = True,
-                default = Label("@bazel_tools//tools/cpp:grep-includes"),
-                cfg = "exec",
-            ),
-            "_is_tonic": attr.bool(
-                doc = "Indicates whether or not Tonic behavior should be enabled.",
-                default = is_tonic,
-            ),
-            "_process_wrapper": attr.label(
-                doc = "A process wrapper for running rustc on all platforms.",
-                default = Label("//util/process_wrapper"),
-                executable = True,
-                allow_single_file = True,
-                cfg = "exec",
-            ),
-            "_prost_process_wrapper": attr.label(
-                doc = "The wrapper script for the Prost protoc plugin.",
-                cfg = "exec",
-                executable = True,
-                default = Label("//proto/prost/private:protoc_wrapper"),
-            ),
-        },
-        fragments = ["cpp"],
-        host_fragments = ["cpp"],
-        toolchains = [
-            TOOLCHAIN_TYPE,
-            "@bazel_tools//tools/cpp:toolchain_type",
-            "@rules_rust//rust:toolchain_type",
-            "@rules_rust//rust/rustfmt:toolchain_type",
-        ],
-        incompatible_use_toolchain_transition = True,
-    )
-
-_rust_prost_aspect = _make_rust_prost_aspect(
+rust_prost_aspect = aspect(
     doc = "An aspect used to generate and compile proto files with Prost.",
-    is_tonic = False,
-)
-
-_rust_tonic_aspect = _make_rust_prost_aspect(
-    doc = "An aspect used to generate and compile proto files with Prost and Tonic.",
-    is_tonic = True,
+    implementation = _rust_prost_aspect_impl,
+    attr_aspects = ["deps"],
+    attrs = {
+        "_cc_toolchain": attr.label(
+            doc = (
+                "In order to use find_cc_toolchain, your rule has to depend " +
+                "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
+                "docs for details."
+            ),
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+        "_collect_cc_coverage": attr.label(
+            default = Label("//util:collect_coverage"),
+            executable = True,
+            cfg = "exec",
+        ),
+        "_error_format": attr.label(
+            default = Label("//:error_format"),
+        ),
+        "_extra_exec_rustc_flag": attr.label(
+            default = Label("//:extra_exec_rustc_flag"),
+        ),
+        "_extra_exec_rustc_flags": attr.label(
+            default = Label("//:extra_exec_rustc_flags"),
+        ),
+        "_extra_rustc_flag": attr.label(
+            default = Label("//:extra_rustc_flag"),
+        ),
+        "_extra_rustc_flags": attr.label(
+            default = Label("//:extra_rustc_flags"),
+        ),
+        "_grep_includes": attr.label(
+            allow_single_file = True,
+            default = Label("@bazel_tools//tools/cpp:grep-includes"),
+            cfg = "exec",
+        ),
+        "_process_wrapper": attr.label(
+            doc = "A process wrapper for running rustc on all platforms.",
+            default = Label("//util/process_wrapper"),
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
+        "_prost_process_wrapper": attr.label(
+            doc = "The wrapper script for the Prost protoc plugin.",
+            cfg = "exec",
+            executable = True,
+            default = Label("//proto/prost/private:protoc_wrapper"),
+        ),
+    },
+    fragments = ["cpp"],
+    host_fragments = ["cpp"],
+    toolchains = [
+        TOOLCHAIN_TYPE,
+        "@bazel_tools//tools/cpp:toolchain_type",
+        "@rules_rust//rust:toolchain_type",
+        "@rules_rust//rust/rustfmt:toolchain_type",
+    ],
+    incompatible_use_toolchain_transition = True,
 )
 
 def _rust_prost_library_impl(ctx):
-    proto_info_provider = TonicProtoInfo if ctx.attr._is_tonic else ProstProtoInfo
-
     proto_dep = ctx.attr.proto
-    rust_proto_info = proto_dep[proto_info_provider]
+    rust_proto_info = proto_dep[ProstProtoInfo]
     dep_variant_info = rust_proto_info.dep_variant_info
 
     return [
@@ -358,37 +336,22 @@
         ),
     ]
 
-def _make_rust_prost_library_rule(doc, is_tonic):
-    return rule(
-        doc = doc,
-        implementation = _rust_prost_library_impl,
-        attrs = {
-            "proto": attr.label(
-                doc = "A `proto_library` target for which to generate Rust gencode.",
-                providers = [ProtoInfo],
-                aspects = [_rust_tonic_aspect if is_tonic else _rust_prost_aspect],
-                mandatory = True,
-            ),
-            "_collect_cc_coverage": attr.label(
-                default = Label("@rules_rust//util:collect_coverage"),
-                executable = True,
-                cfg = "exec",
-            ),
-            "_is_tonic": attr.bool(
-                doc = "Indicates whether or not Tonic behavior should be enabled.",
-                default = is_tonic,
-            ),
-        },
-    )
-
-rust_tonic_library = _make_rust_prost_library_rule(
-    doc = "A rule for generating a Rust library using Prost and Tonic.",
-    is_tonic = True,
-)
-
-rust_prost_library = _make_rust_prost_library_rule(
+rust_prost_library = rule(
     doc = "A rule for generating a Rust library using Prost.",
-    is_tonic = False,
+    implementation = _rust_prost_library_impl,
+    attrs = {
+        "proto": attr.label(
+            doc = "A `proto_library` target for which to generate Rust gencode.",
+            providers = [ProtoInfo],
+            aspects = [rust_prost_aspect],
+            mandatory = True,
+        ),
+        "_collect_cc_coverage": attr.label(
+            default = Label("@rules_rust//util:collect_coverage"),
+            executable = True,
+            cfg = "exec",
+        ),
+    },
 )
 
 def _rust_prost_toolchain_impl(ctx):
diff --git a/proto/prost/private/protoc_wrapper.rs b/proto/prost/private/protoc_wrapper.rs
index 817faac..6c3593e 100644
--- a/proto/prost/private/protoc_wrapper.rs
+++ b/proto/prost/private/protoc_wrapper.rs
@@ -287,17 +287,12 @@
 /// expected to convert proto files into a BTreeMap of
 /// `example.prost.helloworld`: `crate_name::example::prost::helloworld`.
 fn get_extern_paths(
-    descriptor_set_path: &PathBuf,
+    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());
 
-    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");
-
     for file in descriptor_set.file.iter() {
         descriptor_set_file_to_extern_paths(&mut extern_paths, &rust_path, file);
     }
@@ -488,9 +483,6 @@
                 ("--prost_out", value) => {
                     out_dir = Some(PathBuf::from(value));
                 }
-                ("--crate_name", value) => {
-                    crate_name = Some(value.to_string());
-                }
                 ("--package_info_output", value) => {
                     let (key, value) = value
                         .split_once('=')
@@ -509,6 +501,11 @@
                             .expect("Failed to read file")
                             .lines()
                         {
+                            if crate_name.as_ref().unwrap() == "annotations_proto"
+                                && flag.contains("MethodOptions")
+                            {
+                                continue;
+                            }
                             tonic_or_prost_opts.push(format!("extern_path={}", flag.trim()));
                         }
                     }
@@ -620,6 +617,63 @@
     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");
@@ -642,6 +696,15 @@
 
     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 {
@@ -719,9 +782,21 @@
     }
 
     // Locate all prost-generated outputs.
-    let rust_files = find_generated_rust_files(&out_dir);
+    let mut rust_files = find_generated_rust_files(&out_dir);
     if rust_files.is_empty() {
-        panic!("No .rs files were generated by prost.");
+        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)
@@ -784,6 +859,7 @@
 
     use super::*;
 
+    use prost_types::{FieldDescriptorProto, FileDescriptorProto, ServiceDescriptorProto};
     use std::collections::BTreeMap;
 
     #[test]
@@ -1005,6 +1081,131 @@
     }
 
     #[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",
diff --git a/proto/prost/private/providers.bzl b/proto/prost/private/providers.bzl
deleted file mode 100644
index 3405060..0000000
--- a/proto/prost/private/providers.bzl
+++ /dev/null
@@ -1,21 +0,0 @@
-"""Prost and Tonic providers."""
-
-ProstProtoInfo = provider(
-    doc = "Rust Prost provider info",
-    fields = {
-        "dep_variant_info": "DepVariantInfo: For the compiled Rust gencode (also covers its " +
-                            "transitive dependencies)",
-        "package_info": "File: A newline delimited file of `--extern_path` values for protoc.",
-        "transitive_dep_infos": "depset[DepVariantInfo]: Transitive dependencies of the compiled crate.",
-    },
-)
-
-TonicProtoInfo = provider(
-    doc = "Rust Tonic provider info",
-    fields = {
-        "dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " +
-                            "transitive dependencies)",
-        "package_info": "File: A newline delimited file of `--extern_path` values for protoc.",
-        "transitive_dep_infos": "depset[DepVariantInfo]: Transitive dependencies of the compiled crate.",
-    },
-)
diff --git a/proto/prost/private/tests/package_names/BUILD.bazel b/proto/prost/private/tests/package_names/BUILD.bazel
index 526d207..7c9cedc 100644
--- a/proto/prost/private/tests/package_names/BUILD.bazel
+++ b/proto/prost/private/tests/package_names/BUILD.bazel
@@ -1,6 +1,6 @@
 load("@rules_proto//proto:defs.bzl", "proto_library")
 load("@rules_rust//rust:defs.bzl", "rust_test")
-load("//proto/prost:defs.bzl", "rust_tonic_library")
+load("//proto/prost:defs.bzl", "rust_prost_library")
 
 package(default_visibility = ["//proto/prost/private/tests:__subpackages__"])
 
@@ -11,7 +11,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "pkg_empty_rs_proto",
     proto = ":pkg_empty_proto",
 )
@@ -23,7 +23,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "pkg_rs_proto",
     proto = ":pkg_proto",
 )
@@ -35,7 +35,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "pkg_a_rs_proto",
     proto = ":pkg_a_proto",
 )
@@ -47,7 +47,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "pkg_a_b_rs_proto",
     proto = ":pkg_a_b_proto",
 )
diff --git a/proto/prost/private/tests/remote/BUILD.bazel b/proto/prost/private/tests/remote/BUILD.bazel
new file mode 100644
index 0000000..3619c16
--- /dev/null
+++ b/proto/prost/private/tests/remote/BUILD.bazel
@@ -0,0 +1,6 @@
+load("//proto:defs.bzl", "rust_prost_library")
+
+rust_prost_library(
+    name = "annotations_rs_proto",
+    proto = "@com_google_googleapis//google/api:annotations_proto",
+)
diff --git a/proto/prost/private/tests/services/echo/BUILD.bazel b/proto/prost/private/tests/services/echo/BUILD.bazel
index 9a1d3d1..50ae7ab 100644
--- a/proto/prost/private/tests/services/echo/BUILD.bazel
+++ b/proto/prost/private/tests/services/echo/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_proto//proto:defs.bzl", "proto_library")
-load("//proto/prost:defs.bzl", "rust_tonic_library")
+load("//proto/prost:defs.bzl", "rust_prost_library")
 load("//rust:defs.bzl", "rust_binary")
 
 package(default_visibility = ["//proto/prost/private/tests:__subpackages__"])
@@ -11,7 +11,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "echo_rs_proto",
     proto = ":echo_proto",
 )
diff --git a/proto/prost/private/tests/services/helloworld/BUILD.bazel b/proto/prost/private/tests/services/helloworld/BUILD.bazel
index 56b4d77..796492f 100644
--- a/proto/prost/private/tests/services/helloworld/BUILD.bazel
+++ b/proto/prost/private/tests/services/helloworld/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_proto//proto:defs.bzl", "proto_library")
-load("//proto/prost:defs.bzl", "rust_tonic_library")
+load("//proto/prost:defs.bzl", "rust_prost_library")
 load("//rust:defs.bzl", "rust_binary")
 
 package(default_visibility = ["//proto/prost/private/tests:__subpackages__"])
@@ -11,7 +11,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "helloworld_rs_proto",
     proto = ":helloworld_proto",
 )
diff --git a/proto/prost/private/tests/transitive_dependencies/BUILD.bazel b/proto/prost/private/tests/transitive_dependencies/BUILD.bazel
index 6605b1c..5bc3b6a 100644
--- a/proto/prost/private/tests/transitive_dependencies/BUILD.bazel
+++ b/proto/prost/private/tests/transitive_dependencies/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_proto//proto:defs.bzl", "proto_library")
-load("//proto/prost:defs.bzl", "rust_tonic_library")
+load("//proto/prost:defs.bzl", "rust_prost_library")
 load("//rust:defs.bzl", "rust_test")
 
 package(default_visibility = ["//proto/prost/private/tests:__subpackages__"])
@@ -18,7 +18,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "a_rs_proto",
     proto = ":a_proto",
 )
diff --git a/proto/prost/private/tests/transitive_dependencies/b/BUILD.bazel b/proto/prost/private/tests/transitive_dependencies/b/BUILD.bazel
index 51efd12..209a149 100644
--- a/proto/prost/private/tests/transitive_dependencies/b/BUILD.bazel
+++ b/proto/prost/private/tests/transitive_dependencies/b/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_proto//proto:defs.bzl", "proto_library")
-load("//proto/prost:defs.bzl", "rust_tonic_library")
+load("//proto/prost:defs.bzl", "rust_prost_library")
 load("//rust:defs.bzl", "rust_test")
 
 package(default_visibility = ["//proto/prost/private/tests:__subpackages__"])
@@ -16,7 +16,7 @@
     ],
 )
 
-rust_tonic_library(
+rust_prost_library(
     name = "b_rs_proto",
     proto = ":b_proto",
 )
diff --git a/test/deps.bzl b/test/deps.bzl
index 3582161..d2f33f0 100644
--- a/test/deps.bzl
+++ b/test/deps.bzl
@@ -45,3 +45,13 @@
         name = "rules_rust_toolchain_test_target_json",
         target_json = Label("//test/unit/toolchain:toolchain-test-triple.json"),
     )
+
+    maybe(
+        http_archive,
+        name = "com_google_googleapis",
+        urls = [
+            "https://github.com/googleapis/googleapis/archive/18becb1d1426feb7399db144d7beeb3284f1ccb0.zip",
+        ],
+        strip_prefix = "googleapis-18becb1d1426feb7399db144d7beeb3284f1ccb0",
+        sha256 = "b8c487191eb942361af905e40172644eab490190e717c3d09bf83e87f3994fff",
+    )
diff --git a/test/deps_transitive.bzl b/test/deps_transitive.bzl
new file mode 100644
index 0000000..396dc14
--- /dev/null
+++ b/test/deps_transitive.bzl
@@ -0,0 +1,18 @@
+"""Rules rust test dependencies transitive dependencies."""
+
+load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
+
+def rules_rust_test_deps_transitive():
+    switched_rules_by_language(
+        name = "com_google_googleapis_imports",
+        cc = False,
+        csharp = False,
+        gapic = False,
+        go = False,
+        grpc = False,
+        java = False,
+        nodejs = False,
+        php = False,
+        python = False,
+        ruby = False,
+    )