pw_rpc: Support cross-module request and response types

- Load request and response protos from their own files instead of
  assuming they're defined in the same module.
- Don't invoke protoc if no proto file paths are provided.

Change-Id: I44ceedf0ddcb0751368d41d75de3316cf21c34f2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/26740
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py b/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
index 3286ccc..e43a7cf 100644
--- a/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
+++ b/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
@@ -259,7 +259,8 @@
             else:
                 modules.append(proto)
 
-        modules += compile_and_import(paths)
+        if paths:
+            modules += compile_and_import(paths)
         return Library(modules)
 
     @classmethod
diff --git a/pw_rpc/py/pw_rpc/client.py b/pw_rpc/py/pw_rpc/client.py
index 3624989..3661214 100644
--- a/pw_rpc/py/pw_rpc/client.py
+++ b/pw_rpc/py/pw_rpc/client.py
@@ -280,7 +280,7 @@
                      modules: Iterable):
         return cls(
             impl, channels,
-            (Service.from_descriptor(module, service) for module in modules
+            (Service.from_descriptor(service) for module in modules
              for service in module.DESCRIPTOR.services_by_name.values()))
 
     def __init__(self, impl: ClientImpl, channels: Iterable[Channel],
diff --git a/pw_rpc/py/pw_rpc/descriptors.py b/pw_rpc/py/pw_rpc/descriptors.py
index 03ae62b..7ac7af1 100644
--- a/pw_rpc/py/pw_rpc/descriptors.py
+++ b/pw_rpc/py/pw_rpc/descriptors.py
@@ -19,7 +19,7 @@
 from typing import (Any, Callable, Collection, Dict, Generic, Iterable,
                     Iterator, Tuple, TypeVar, Union)
 
-from google.protobuf import descriptor_pb2
+from google.protobuf import descriptor_pb2, message_factory
 from google.protobuf.descriptor import FieldDescriptor
 from pw_protobuf_compiler import python_protos
 
@@ -48,13 +48,13 @@
         return f'{self.package}.{self.name}'
 
     @classmethod
-    def from_descriptor(cls, module, descriptor):
+    def from_descriptor(cls, descriptor):
         service = cls(descriptor.name, ids.calculate(descriptor.full_name),
                       descriptor.file.package, None)
         object.__setattr__(
             service, 'methods',
             Methods(
-                Method.from_descriptor(module, method_descriptor, service)
+                Method.from_descriptor(method_descriptor, service)
                 for method_descriptor in descriptor.methods))
 
         return service
@@ -124,14 +124,18 @@
     response_type: Any
 
     @classmethod
-    def from_descriptor(cls, module, descriptor, service: Service):
+    def from_descriptor(cls, descriptor, service: Service):
+        input_factory = message_factory.MessageFactory(
+            descriptor.input_type.file.pool)
+        output_factory = message_factory.MessageFactory(
+            descriptor.output_type.file.pool)
         return Method(
             service,
             descriptor.name,
             ids.calculate(descriptor.name),
             *_streaming_attributes(descriptor),
-            getattr(module, descriptor.input_type.name),
-            getattr(module, descriptor.output_type.name),
+            input_factory.GetPrototype(descriptor.input_type),
+            output_factory.GetPrototype(descriptor.output_type),
         )
 
     class Type(enum.Enum):