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/docs.rst b/pw_protobuf_compiler/docs.rst
index e33099e..4bb4cdc 100644
--- a/pw_protobuf_compiler/docs.rst
+++ b/pw_protobuf_compiler/docs.rst
@@ -16,18 +16,22 @@
Protobuf code generation is currently supported for the following generators:
-+-------------+------------+---------------------------------------------------+
-| Generator | Code | Notes |
-+-------------+------------+---------------------------------------------------+
-| pw_protobuf | ``pwpb`` | Compiles using ``pw_protobuf``. |
-+-------------+------------+---------------------------------------------------+
-| Go | ``go`` | Compiles using the standard Go protobuf plugin |
-| | | with gRPC service support. |
-+-------------+------------+---------------------------------------------------+
-| Nanopb | ``nanopb`` | Compiles using Nanopb. The build argument |
-| | | ``dir_pw_third_party_nanopb`` must be set to |
-| | | point to a local nanopb installation. |
-+-------------+------------+---------------------------------------------------+
++-------------+----------------+-----------------------------------------------+
+| Generator | Code | Notes |
++-------------+----------------+-----------------------------------------------+
+| pw_protobuf | ``pwpb`` | Compiles using ``pw_protobuf``. |
++-------------+----------------+-----------------------------------------------+
+| Go | ``go`` | Compiles using the standard Go protobuf |
+| | | plugin with gRPC service support. |
++-------------+----------------+-----------------------------------------------+
+| Nanopb | ``nanopb`` | Compiles using Nanopb. The build argument |
+| | | ``dir_pw_third_party_nanopb`` must be set to |
+| | | point to a local nanopb installation. |
++-------------+----------------+-----------------------------------------------+
+| Nanopb RPC | ``nanopb_rpc`` | Compiles pw_rpc service code for a nanopb |
+| | | server. Requires the nanopb generator to be |
+| | | configured as well. |
++-------------+----------------+-----------------------------------------------+
The build variable ``pw_protobuf_GENERATORS`` tells the module the generators
for which it should compile code. It is defined as a list of generator codes.
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("/",
diff --git a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
index 96c8179..79c369a 100644
--- a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
+++ b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
@@ -77,12 +77,20 @@
]
+def protoc_nanopb_rpc_args(args: argparse.Namespace) -> List[str]:
+ return [
+ '--plugin', f'protoc-gen-custom={shutil.which("pw_rpc_codegen")}',
+ '--custom_out', args.out_dir
+ ]
+
+
# Default additional protoc arguments for each supported language.
# TODO(frolv): Make these overridable with a command-line argument.
DEFAULT_PROTOC_ARGS: Dict[str, Callable[[argparse.Namespace], List[str]]] = {
'cc': protoc_cc_args,
'go': protoc_go_args,
'nanopb': protoc_nanopb_args,
+ 'nanopb_rpc': protoc_nanopb_rpc_args,
}
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 2cc9a8f..62fbe3c 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -127,6 +127,15 @@
sources = [ "pw_rpc_protos/packet.proto" ]
}
+# Source files for pw_protobuf's protoc plugin.
+pw_input_group("nanopb_protoc_plugin") {
+ inputs = [
+ "py/pw_rpc/codegen_nanopb.py",
+ "py/pw_rpc/plugin.py",
+ "py/pw_rpc/ids.py",
+ ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
@@ -137,6 +146,7 @@
":channel_test",
":packet_test",
":server_test",
+ "nanopb:codegen_test",
"nanopb:method_test",
]
}
diff --git a/pw_rpc/nanopb/BUILD.gn b/pw_rpc/nanopb/BUILD.gn
index a8b0cc8..f5b4689 100644
--- a/pw_rpc/nanopb/BUILD.gn
+++ b/pw_rpc/nanopb/BUILD.gn
@@ -45,3 +45,12 @@
sources = [ "method_test.cc" ]
enable_if = dir_pw_third_party_nanopb != ""
}
+
+pw_test("codegen_test") {
+ deps = [
+ "..:nanopb_server",
+ "..:test_protos_nanopb_rpc",
+ ]
+ sources = [ "codegen_test.cc" ]
+ enable_if = dir_pw_third_party_nanopb != ""
+}
diff --git a/pw_rpc/py/pw_rpc/codegen_nanopb.py b/pw_rpc/py/pw_rpc/codegen_nanopb.py
index 27b2573..f9f3f05 100644
--- a/pw_rpc/py/pw_rpc/codegen_nanopb.py
+++ b/pw_rpc/py/pw_rpc/codegen_nanopb.py
@@ -112,8 +112,8 @@
f'{RPC_NAMESPACE}::ServerWriter<T>;')
output.write_line()
- output.write_line(f'constexpr {service.name()}()'
- f' : {base_class}(kServiceId, kMethods) {{}}')
+ output.write_line(f'constexpr {service.name()}()')
+ output.write_line(f' : {base_class}(kServiceId, kMethods) {{}}')
output.write_line()
output.write_line(
@@ -121,6 +121,10 @@
output.write_line(f'{service.name()}& operator='
f'(const {service.name()}&) = delete;')
+ output.write_line()
+ output.write_line(f'static constexpr const char* name() '
+ f'{{ return "{service.name()}"; }}')
+
for method in service.methods():
_generate_code_for_method(method, output)
@@ -132,8 +136,6 @@
output.write_line(
f'static constexpr uint32_t kServiceId = {hex(service_name_hash)};'
)
- output.write_line(
- f'static constexpr char* kServiceName = "{service.name()}";')
output.write_line()
output.write_line(