pw_protobuf_compiler: Add nanopb RPC generator

This adds a GN protobuf generator for compiling nanopb RPC code using
the pw_rpc compiler plugin.

Change-Id: Ida27ef6d2adf396a352227493f0982de2bfe7573
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/13340
Commit-Queue: Alexei Frolov <frolv@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 09ebaa6..b9edfad 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -24,7 +24,7 @@
   # pw_proto_library template to determine which build targets to create.
   #
   # Supported generators:
-  #   "pwpb", "nanopb", "go"
+  #   "pwpb", "nanopb", "nanopb_rpc", "go"
   pw_protobuf_GENERATORS = [
     "pwpb",
     "go",
@@ -97,6 +97,78 @@
   }
 }
 
+# Generates nanopb RPC code for proto files, creating a source_set of the
+# generated files. This is internal and should not be used outside of this file.
+# Use pw_proto_library instead.
+#
+# Args:
+#  protos: List of input .proto files.
+#
+template("_pw_nanopb_rpc_proto_library") {
+  assert(defined(dir_pw_third_party_nanopb) && dir_pw_third_party_nanopb != "",
+         "\$dir_pw_third_party_nanopb must be set to compile nanopb protobufs")
+
+  _proto_gen_dir = "$root_gen_dir/protos"
+  _module_path = get_path_info(".", "abspath")
+  _relative_proto_paths = rebase_path(invoker.protos, _module_path)
+
+  _outputs = []
+  foreach(_proto, _relative_proto_paths) {
+    _output_h = string_replace(_proto, ".proto", "_rpc.pb.h")
+    _outputs += [ "$_proto_gen_dir/$_output_h" ]
+  }
+
+  # Create a target which runs protoc configured with the nanopb_rpc plugin to
+  # generate the C++ proto RPC headers.
+  _gen_target = "${target_name}_gen"
+  pw_python_script(_gen_target) {
+    forward_variables_from(invoker, _forwarded_vars)
+    script = _gen_script_path
+    args = [
+             "--language",
+             "nanopb_rpc",
+             "--module-path",
+             _module_path,
+             "--include-paths",
+             "$dir_pw_third_party_nanopb/generator/proto",
+             "--include-file",
+             invoker.include_file,
+             "--out-dir",
+             _proto_gen_dir,
+           ] + get_path_info(invoker.protos, "abspath")
+    inputs = invoker.protos
+    outputs = _outputs
+
+    deps = invoker.deps
+    if (defined(invoker.protoc_deps)) {
+      deps += invoker.protoc_deps
+    }
+  }
+
+  # For C++ proto files, the generated proto directory is added as an include
+  # path for the code. This requires using "all_dependent_configs" to force the
+  # include on any code that transitively depends on the generated protos.
+  _include_root = rebase_path(get_path_info(".", "abspath"), "//")
+  _include_config_target = "${target_name}_includes"
+  config(_include_config_target) {
+    include_dirs = [
+      "$_proto_gen_dir",
+      "$_proto_gen_dir/$_include_root",
+    ]
+  }
+
+  # Create a library with the generated source files.
+  pw_source_set(target_name) {
+    all_dependent_configs = [ ":$_include_config_target" ]
+    deps = [ ":$_gen_target" ]
+    public_deps = [
+                    dir_pw_third_party_nanopb,
+                    "$dir_pw_rpc:nanopb_server",
+                  ] + invoker.gen_deps
+    public = get_target_outputs(":$_gen_target")
+  }
+}
+
 # Generates nanopb code for proto files, creating a source_set of the generated
 # files. This is internal and should not be used outside of this file. Use
 # pw_proto_library instead.
@@ -272,11 +344,27 @@
     _deps += [ ":$_input_target_name" ]
   }
 
-  foreach(_gen, pw_protobuf_GENERATORS) {
+  # If the nanopb_rpc generator is selected, make sure that nanopb is also
+  # selected.
+  has_nanopb_rpc = pw_protobuf_GENERATORS + [ "nanopb_rpc" ] -
+                   [ "nanopb_rpc" ] != pw_protobuf_GENERATORS
+  if (has_nanopb_rpc) {
+    _generators =
+        pw_protobuf_GENERATORS + [ "nanopb" ] - [ "nanopb" ] + [ "nanopb" ]
+  } else {
+    _generators = pw_protobuf_GENERATORS
+  }
+
+  foreach(_gen, _generators) {
     _lang_target = "${target_name}_${_gen}"
     _gen_deps = []
     if (defined(invoker.deps)) {
       _gen_deps = process_file_template(invoker.deps, "{{source}}_${_gen}")
+
+      if (_gen == "nanopb_rpc") {
+        # Generated RPC code also depends on the core generated protos.
+        _gen_deps += process_file_template(invoker.deps, "{{source}}_nanopb")
+      }
     }
 
     if (_gen == "pwpb") {
@@ -291,6 +379,18 @@
         # generated code if they are modified.
         protoc_deps = [ "$dir_pw_protobuf:codegen_protoc_plugin" ]
       }
+    } else if (_gen == "nanopb_rpc") {
+      _pw_nanopb_rpc_proto_library(_lang_target) {
+        forward_variables_from(invoker, _forwarded_vars)
+        protos = invoker.sources
+        deps = _deps
+        include_file = _include_metadata_file
+        gen_deps = _gen_deps
+
+        # List the pw_protobuf plugin's files as a dependency to recompile
+        # generated code if they are modified.
+        protoc_deps = [ "$dir_pw_rpc:nanopb_protoc_plugin" ]
+      }
     } else if (_gen == "nanopb") {
       _pw_nanopb_proto_library(_lang_target) {
         forward_variables_from(invoker, _forwarded_vars)
@@ -322,11 +422,12 @@
   _protobuf_generators = [
     "pwpb",
     "nanopb",
+    "nanopb_rpc",
     "go",
   ]
 
   # Create stub versions of the proto library for other protobuf generators.
-  foreach(_gen, _protobuf_generators - pw_protobuf_GENERATORS) {
+  foreach(_gen, _protobuf_generators - _generators) {
     pw_python_script("${target_name}_${_gen}") {
       forward_variables_from(invoker, _forwarded_vars)
       script = string_join("/",