Skip to content

Commit

Permalink
pw_rpc: Support pw_protobuf
Browse files Browse the repository at this point in the history
Adds an additional `pwpb_rpc` option to pw_proto_library, along with the
pw_rpc support for encoding and decoding to pw_protobuf message structs.

For simple services, the C++ types used by pw_protobuf result in a
cleaner code, for example taking advantage of pw::Vector::operator=()

  #include "pw_rpc/echo.rpc.pwpb.h"

  namespace pw::rpc {

  class EchoService final
      : public pw_rpc::pwpb::EchoService::Service<EchoService> {
   public:
    Status Echo(const EchoMessage::Message& request,
                EchoMessage::Message& response) {
      response.msg = request.msg;
      return OkStatus();
    }
  };

  }  // namespace pw::rpc

Once complex callbacks are involved, the C++ types and pw_protobuf's
codegen result in vastly cleaner code compared to other protobuf
libraries. For example handling a repeated field without a maximum size
through an encoder callback:

  Status WriteValues(const Request::Message&,
                     Response::Message& response) {
    response.values.SetEncoder(
      [&this](Response::StreamEncoder& encoder) {
        return encoder.WriteValues(values_);
      });
    return OkStatus();
  }

  pw::Vector<uint32_t, 64> values_;

Change-Id: I4d6eba4b8202e6deb7e66b80f6a57af9f0207bdf
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/90421
Pigweed-Auto-Submit: Scott James Remnant <keybuk@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Scott James Remnant <keybuk@google.com>
  • Loading branch information
keybuk authored and CQ Bot Account committed Apr 28, 2022
1 parent 982018f commit b8c03b2
Show file tree
Hide file tree
Showing 55 changed files with 6,530 additions and 42 deletions.
2 changes: 1 addition & 1 deletion pw_protobuf/public/pw_protobuf/internal/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ static_assert(sizeof(MessageField) <= sizeof(size_t) * 4,
// a field.
template <typename StreamEncoder, typename StreamDecoder>
union Callback {
Callback() : encode_() {}
constexpr Callback() : encode_() {}
~Callback() { encode_ = nullptr; }

// Set the encoder callback.
Expand Down
8 changes: 8 additions & 0 deletions pw_protobuf/py/pw_protobuf/proto_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ def proto_path(self) -> str:
path = '.'.join(self._attr_hierarchy(lambda node: node.name(), None))
return path.lstrip('.')

def pwpb_struct(self) -> str:
"""Name of the pw_protobuf struct for this proto."""
return '::' + self.cpp_namespace() + '::Message'

def pwpb_table(self) -> str:
"""Name of the pw_protobuf table constant for this proto."""
return '::' + self.cpp_namespace() + '::kMessageFields'

def nanopb_fields(self) -> str:
"""Name of the Nanopb variable that represents the proto fields."""
return self._nanopb_name() + '_fields'
Expand Down
7 changes: 6 additions & 1 deletion pw_protobuf_compiler/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ package(default_visibility = ["//visibility:public"])

licenses(["notice"])

# TODO(frolv): Figure out how to support nanopb codegen in Bazel.
# TODO(frolv): Figure out how to support nanopb and pwpb codegen in Bazel.
filegroup(
name = "nanopb_test",
srcs = ["nanopb_test.cc"],
)

filegroup(
name = "pwpb_test",
srcs = ["pwpb_test.cc"],
)

py_proto_library(
name = "pw_protobuf_compiler_protos",
srcs = [
Expand Down
15 changes: 14 additions & 1 deletion pw_protobuf_compiler/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ pw_doc_group("docs") {
}

pw_test_group("tests") {
tests = [ ":nanopb_test" ]
tests = [
":nanopb_test",
":pwpb_test",
]
}

pw_test("nanopb_test") {
Expand All @@ -42,6 +45,16 @@ pw_proto_library("nanopb_test_protos") {
}
}

pw_test("pwpb_test") {
deps = [ ":pwpb_test_protos.pwpb" ]
sources = [ "pwpb_test.cc" ]
}

pw_proto_library("pwpb_test_protos") {
sources = [ "pw_protobuf_compiler_pwpb_protos/pwpb_test.proto" ]
inputs = [ "pw_protobuf_compiler_pwpb_protos/pwpb_test.options" ]
}

pw_proto_library("test_protos") {
sources = [
"pw_protobuf_compiler_protos/nested/more_nesting/test.proto",
Expand Down
16 changes: 16 additions & 0 deletions pw_protobuf_compiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@
include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)

pw_proto_library(pw_protobuf_compiler.pwpb_test_protos
SOURCES
pw_protobuf_compiler_pwpb_protos/pwpb_test.proto
INPUTS
pw_protobuf_compiler_pwpb_protos/pwpb_test.options
)

pw_add_test(pw_protobuf_compiler.pwpb_test
SOURCES
pwpb_test.cc
DEPS
pw_protobuf_compiler.pwpb_test_protos.pwpb
GROUPS
pw_protobuf_compiler
)

if(NOT "${dir_pw_third_party_nanopb}" STREQUAL "")
pw_proto_library(pw_protobuf_compiler.nanopb_test_protos
SOURCES
Expand Down
8 changes: 7 additions & 1 deletion pw_protobuf_compiler/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Protobuf code generation is currently supported for the following generators:
+-------------+----------------+-----------------------------------------------+
| pw_protobuf | ``pwpb`` | Compiles using ``pw_protobuf``. |
+-------------+----------------+-----------------------------------------------+
| pw_protobuf | ``pwpb_rpc`` | Compiles pw_rpc service and client code for |
| RPC | | ``pw_protobuf``. |
+-------------+----------------+-----------------------------------------------+
| Nanopb | ``nanopb`` | Compiles using Nanopb. The build argument |
| | | ``dir_pw_third_party_nanopb`` must be set to |
| | | point to a local nanopb installation. |
Expand Down Expand Up @@ -81,6 +84,7 @@ GN supports the following compiled proto libraries via the specified
sub-targets generated by a ``pw_proto_library``.

* ``${target_name}.pwpb`` - Generated C++ pw_protobuf code
* ``${target_name}.pwpb_rpc`` - Generated C++ pw_protobuf pw_rpc code
* ``${target_name}.nanopb`` - Generated C++ nanopb code (requires Nanopb)
* ``${target_name}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires
Nanopb)
Expand Down Expand Up @@ -330,6 +334,7 @@ CMake supports the following compiled proto libraries via the specified
sub-targets generated by a ``pw_proto_library``.

* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code
* ``${NAME}.pwpb_rpc`` - Generated C++ pw_protobuf pw_rpc code
* ``${NAME}.nanopb`` - Generated C++ nanopb code (requires Nanopb)
* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires Nanopb)
* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library)
Expand Down Expand Up @@ -416,7 +421,7 @@ compile them. e.g.
name = "my_lib",
srcs = ["my/lib.cc"],
# This target depends on all generated proto targets
# e.g. name.{pwpb, nanopb, raw_rpc, nanopb_rpc}
# e.g. name.{pwpb, pwpb_rpc, nanopb, raw_rpc, nanopb_rpc}
deps = [":my_cc_proto"],
)
Expand All @@ -440,6 +445,7 @@ Bazel supports the following compiled proto libraries via the specified
sub-targets generated by a ``pw_proto_library``.

* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code
* ``${NAME}.pwpb_rpc`` - Generated C++ pw_protobuf pw_rpc code
* ``${NAME}.nanopb`` - Generated C++ nanopb code
* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library)
* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code
Expand Down
38 changes: 37 additions & 1 deletion pw_protobuf_compiler/proto.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ include_guard(GLOBAL)
#
# This function also creates libraries for generating pw_rpc code:
#
# ${NAME}.pwpb_rpc - generates pw_protobuf pw_rpc code
# ${NAME}.nanopb_rpc - generates Nanopb pw_rpc code
# ${NAME}.raw_rpc - generates raw pw_rpc (no protobuf library) code
# ${NAME}.pwpb_rpc - (Not implemented) generates pw_protobuf pw_rpc code
#
# Args:
#
Expand Down Expand Up @@ -105,6 +105,8 @@ function(pw_proto_library NAME)
# Create a protobuf target for each supported protobuf library.
_pw_pwpb_library(
"${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
_pw_pwpb_rpc_library(
"${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
_pw_raw_rpc_library(
"${NAME}" "${sources}" "${inputs}" "${arg_DEPS}" "${include_file}" "${out_dir}")
_pw_nanopb_library(
Expand Down Expand Up @@ -200,6 +202,40 @@ function(_pw_pwpb_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb")
endfunction(_pw_pwpb_library)

# Internal function that creates a pwpb_rpc library.
function(_pw_pwpb_rpc_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
# Determine the names of the output files.
list(TRANSFORM DEPS APPEND .pwpb_rpc)

_pw_generate_protos("${NAME}"
pwpb_rpc
"$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_pwpb.py"
".rpc.pwpb.h"
"${INCLUDE_FILE}"
"${OUT_DIR}"
"${SOURCES}"
"${INPUTS}"
"${DEPS}"
)

# Create the library with the generated source files.
add_library("${NAME}.pwpb_rpc" INTERFACE)
target_include_directories("${NAME}.pwpb_rpc"
INTERFACE
"${OUT_DIR}/pwpb_rpc"
)
target_link_libraries("${NAME}.pwpb_rpc"
INTERFACE
"${NAME}.pwpb"
pw_build
pw_rpc.pwpb.client
pw_rpc.pwpb.method_union
pw_rpc.server
${DEPS}
)
add_dependencies("${NAME}.pwpb_rpc" "${NAME}._generate.pwpb_rpc")
endfunction(_pw_pwpb_rpc_library)

# Internal function that creates a raw_rpc proto library.
function(_pw_raw_rpc_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
list(TRANSFORM DEPS APPEND .raw_rpc)
Expand Down
49 changes: 49 additions & 0 deletions pw_protobuf_compiler/proto.gni
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,38 @@ template("_pw_invoke_protoc") {
# Generates pw_protobuf C++ 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.
template("_pw_pwpb_rpc_proto_library") {
# Create a target which runs protoc configured with the pwpb_rpc plugin to
# generate the C++ proto RPC headers.
_pw_invoke_protoc(target_name) {
forward_variables_from(invoker, "*", _forwarded_vars)
language = "pwpb_rpc"
plugin = "$dir_pw_rpc/py/pw_rpc/plugin_pwpb.py"
python_deps = [ "$dir_pw_rpc/py" ]
}

# Create a library with the generated source files.
config("$target_name._include_path") {
include_dirs = [ "${invoker.base_out_dir}/pwpb_rpc" ]
visibility = [ ":*" ]
}

pw_source_set(target_name) {
forward_variables_from(invoker, _forwarded_vars)
public_configs = [ ":$target_name._include_path" ]
deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
public_deps = [
":${invoker.base_target}.pwpb",
"$dir_pw_protobuf",
"$dir_pw_rpc:server",
"$dir_pw_rpc/pwpb:client_api",
"$dir_pw_rpc/pwpb:server_api",
] + invoker.deps
public = invoker.outputs
check_includes = false
}
}

template("_pw_pwpb_proto_library") {
_pw_invoke_protoc(target_name) {
forward_variables_from(invoker, "*", _forwarded_vars)
Expand Down Expand Up @@ -532,6 +564,22 @@ template("pw_proto_library") {

# Enumerate all of the protobuf generator targets.

_pw_pwpb_rpc_proto_library("$target_name.pwpb_rpc") {
forward_variables_from(invoker, _forwarded_vars)
forward_variables_from(_common, "*")

deps = []
foreach(dep, _deps) {
_base = get_label_info(dep, "label_no_toolchain")
deps += [ "$_base.pwpb_rpc(" + get_label_info(dep, "toolchain") + ")" ]
}

outputs = []
foreach(name, _source_names) {
outputs += [ "$base_out_dir/pwpb_rpc/$_prefix/${name}.rpc.pwpb.h" ]
}
}

_pw_pwpb_proto_library("$target_name.pwpb") {
forward_variables_from(invoker, _forwarded_vars)
forward_variables_from(_common, "*")
Expand Down Expand Up @@ -652,6 +700,7 @@ template("pw_proto_library") {
# All supported pw_protobuf generators.
_protobuf_generators = [
"pwpb",
"pwpb_rpc",
"nanopb",
"nanopb_rpc",
"raw_rpc",
Expand Down
32 changes: 31 additions & 1 deletion pw_protobuf_compiler/pw_proto_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def pw_proto_library(name = "", deps = [], nanopb_options = None):
The pw_proto_library generates the following targets in this example:
"benchmark_pw_proto.pwpb": C++ library exposing the "benchmark.pwpb.h" header.
"benchmark_pw_proto.pwpb_rpc": C++ library exposing the
"benchmark.rpc.pwpb.h" header.
"benchmark_pw_proto.raw_rpc": C++ library exposing the "benchmark.raw_rpc.h"
header.
"benchmark_pw_proto.nanopb": C++ library exposing the "benchmark.pb.h"
Expand All @@ -108,9 +110,11 @@ def pw_proto_library(name = "", deps = [], nanopb_options = None):
deps = deps,
)

# The rpc.pb.h header depends on the generated nanopb code.
# The rpc.pb.h header depends on the generated nanopb or pwpb code.
if info["include_nanopb_dep"]:
lib_deps = info["deps"] + [":" + name + ".nanopb"]
elif info["include_pwpb_dep"]:
lib_deps = info["deps"] + [":" + name + ".pwpb"]
else:
lib_deps = info["deps"]

Expand Down Expand Up @@ -235,6 +239,18 @@ _pw_proto_library = rule(
},
)

_pw_pwpb_rpc_proto_compiler_aspect = _proto_compiler_aspect("rpc.pwpb.h", "//pw_rpc/py:plugin_pwpb")

_pw_pwpb_rpc_proto_library = rule(
implementation = _impl_pw_proto_library,
attrs = {
"deps": attr.label_list(
providers = [ProtoInfo],
aspects = [_pw_pwpb_rpc_proto_compiler_aspect],
),
},
)

_pw_raw_rpc_proto_compiler_aspect = _proto_compiler_aspect("raw_rpc.pb.h", "//pw_rpc/py:plugin_raw")

_pw_raw_rpc_proto_library = rule(
Expand Down Expand Up @@ -267,6 +283,18 @@ PIGWEED_PLUGIN = {
"//pw_protobuf:pw_protobuf",
],
"include_nanopb_dep": False,
"include_pwpb_dep": False,
},
"pwpb_rpc": {
"compiler": _pw_pwpb_rpc_proto_library,
"deps": [
"//pw_protobuf:pw_protobuf",
"//pw_rpc",
"//pw_rpc/raw:client_api",
"//pw_rpc/raw:server_api",
],
"include_nanopb_dep": False,
"include_pwpb_dep": True,
},
"raw_rpc": {
"compiler": _pw_raw_rpc_proto_library,
Expand All @@ -276,6 +304,7 @@ PIGWEED_PLUGIN = {
"//pw_rpc/raw:server_api",
],
"include_nanopb_dep": False,
"include_pwpb_dep": False,
},
"nanopb_rpc": {
"compiler": _pw_nanopb_rpc_proto_library,
Expand All @@ -285,5 +314,6 @@ PIGWEED_PLUGIN = {
"//pw_rpc/nanopb:server_api",
],
"include_nanopb_dep": True,
"include_pwpb_dep": False,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

pw.protobuf_compiler.Point.name max_size:16
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
syntax = "proto3";

package pw.protobuf_compiler;

message Point {
uint32 x = 1;
uint32 y = 2;
string name = 3;
};
Loading

0 comments on commit b8c03b2

Please sign in to comment.