1.. _module-pw_protobuf_compiler: 2 3==================== 4pw_protobuf_compiler 5==================== 6The Protobuf compiler module provides build system integration and wrapper 7scripts for generating source code for Protobuf definitions. 8 9-------------------- 10Protobuf compilation 11-------------------- 12 13Generator support 14================= 15Protobuf code generation is currently supported for the following generators: 16 17+-------------+----------------+-----------------------------------------------+ 18| Generator | Code | Notes | 19+-------------+----------------+-----------------------------------------------+ 20| pw_protobuf | ``pwpb`` | Compiles using ``pw_protobuf``. | 21+-------------+----------------+-----------------------------------------------+ 22| pw_protobuf | ``pwpb_rpc`` | Compiles pw_rpc service and client code for | 23| RPC | | ``pw_protobuf``. | 24+-------------+----------------+-----------------------------------------------+ 25| Nanopb | ``nanopb`` | Compiles using Nanopb. The build argument | 26| | | ``dir_pw_third_party_nanopb`` must be set to | 27| | | point to a local nanopb installation. | 28+-------------+----------------+-----------------------------------------------+ 29| Nanopb RPC | ``nanopb_rpc`` | Compiles pw_rpc service and client code for | 30| | | nanopb. Requires a nanopb installation. | 31+-------------+----------------+-----------------------------------------------+ 32| Raw RPC | ``raw_rpc`` | Compiles raw binary pw_rpc service code. | 33+-------------+----------------+-----------------------------------------------+ 34| Go | ``go`` | Compiles using the standard Go protobuf | 35| | | plugin with gRPC service support. | 36+-------------+----------------+-----------------------------------------------+ 37| Python | ``python`` | Compiles using the standard Python protobuf | 38| | | plugin, creating a ``pw_python_package``. | 39+-------------+----------------+-----------------------------------------------+ 40| Typescript | ``typescript`` | Compilation is supported in Bazel via | 41| | | @rules_proto_grpc. ProtoCollection provides | 42| | | convience methods for proto descriptors. | 43+-------------+----------------+-----------------------------------------------+ 44 45GN template 46=========== 47This module provides a ``pw_proto_library`` GN template that defines a 48collection of protobuf files that should be compiled together. The template 49creates a sub-target for each supported generator, named 50``<target_name>.<generator>``. These sub-targets generate their respective 51protobuf code, and expose it to the build system appropriately (e.g. a 52``pw_source_set`` for C/C++). 53 54For example, given the following target: 55 56.. code-block:: 57 58 pw_proto_library("test_protos") { 59 sources = [ "my_test_protos/test.proto" ] 60 } 61 62``test_protos.pwpb`` compiles code for pw_protobuf, and ``test_protos.nanopb`` 63compiles using Nanopb (if it's installed). 64 65Protobuf code is only generated when a generator sub-target is listed as a 66dependency of another GN target. 67 68GN permits using abbreviated labels when the target name matches the directory 69name (e.g. ``//foo`` for ``//foo:foo``). For consistency with this, the 70sub-targets for each generator are aliased to the directory when the target name 71is the same. For example, these two labels are equivalent: 72 73.. code-block:: 74 75 //path/to/my_protos:my_protos.pwpb 76 //path/to/my_protos:pwpb 77 78``pw_python_package`` subtargets are also available on the ``python`` subtarget: 79 80.. code-block:: 81 82 //path/to/my_protos:my_protos.python.lint 83 //path/to/my_protos:python.lint 84 85**Supported Codegen** 86 87GN supports the following compiled proto libraries via the specified 88sub-targets generated by a ``pw_proto_library``. 89 90* ``${target_name}.pwpb`` - Generated C++ pw_protobuf code 91* ``${target_name}.pwpb_rpc`` - Generated C++ pw_protobuf pw_rpc code 92* ``${target_name}.nanopb`` - Generated C++ nanopb code (requires Nanopb) 93* ``${target_name}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires 94 Nanopb) 95* ``${target_name}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf 96 library) 97* ``${target_name}.go`` - Generated GO protobuf libraries 98* ``${target_name}.python`` - Generated Python protobuf libraries 99 100**Arguments** 101 102* ``sources``: List of input .proto files. 103* ``deps``: List of other pw_proto_library dependencies. 104* ``other_deps``: List of other non-proto dependencies. 105* ``inputs``: Other files on which the protos depend (e.g. nanopb ``.options`` 106 files). 107* ``prefix``: A prefix to add to the source protos prior to compilation. For 108 example, a source called ``"foo.proto"`` with ``prefix = "nested"`` will be 109 compiled with protoc as ``"nested/foo.proto"``. 110* ``strip_prefix``: Remove this prefix from the source protos. All source and 111 input files must be nested under this path. 112* ``python_package``: Label of Python package to which to add the proto modules. 113 The .python subtarget will redirect to this package. 114* ``enabled_targets``: List of sub-targets to enable (see Supported Codegen), 115 e.g. ``["pwpb", "raw_rpc"]``. By default, all sub-targets are enabled. The 116 enabled sub-targets are built only as requested by the build system, but it 117 may be necessary to explicitly disable an unused sub-target if it conflicts 118 with another target in the same package. (For example, ``nanopb`` codegen can 119 conflict with the default C++ codegen provided by ``protoc``.) 120 TODO: b/235132083 - Remove this argument once we've removed the file-name 121 conflict between nanopb and protoc code generators. 122 123**Example** 124 125.. code-block:: 126 127 import("$dir_pw_protobuf_compiler/proto.gni") 128 129 pw_proto_library("my_protos") { 130 sources = [ 131 "my_protos/foo.proto", 132 "my_protos/bar.proto", 133 ] 134 } 135 136 pw_proto_library("my_other_protos") { 137 sources = [ "some/other/path/baz.proto" ] # imports foo.proto 138 139 # This removes the "some/other/path" prefix from the proto files. 140 strip_prefix = "some/other/path" 141 142 # This adds the "my_other_protos/" prefix to the proto files. 143 prefix = "my_other_protos" 144 145 # Proto libraries depend on other proto libraries directly. 146 deps = [ ":my_protos" ] 147 } 148 149 source_set("my_cc_code") { 150 sources = [ 151 "foo.cc", 152 "bar.cc", 153 "baz.cc", 154 ] 155 156 # When depending on protos in a source_set, specify the generator suffix. 157 deps = [ ":my_other_protos.pwpb" ] 158 } 159 160From C++, ``baz.proto`` included as follows: 161 162.. code-block:: cpp 163 164 #include "my_other_protos/baz.pwpb.h" 165 166From Python, ``baz.proto`` is imported as follows: 167 168.. code-block:: python 169 170 from my_other_protos import baz_pb2 171 172Proto file structure 173-------------------- 174Protobuf source files must be nested under another directory when they are 175compiled. This ensures that they can be packaged properly in Python. 176 177Using ``prefix`` and ``strip_prefix`` together allows remapping proto files to 178a completely different path. This can be useful when working with protos defined 179in external libraries. For example, consider this proto library: 180 181.. code-block:: 182 183 pw_proto_library("external_protos") { 184 sources = [ 185 "//other/external/some_library/src/protos/alpha.proto", 186 "//other/external/some_library/src/protos/beta.proto, 187 "//other/external/some_library/src/protos/internal/gamma.proto", 188 ] 189 strip_prefix = "//other/external/some_library/src/protos" 190 prefix = "some_library" 191 } 192 193These protos will be compiled by protoc as if they were in this file structure: 194 195.. code-block:: 196 197 some_library/ 198 ├── alpha.proto 199 ├── beta.proto 200 └── internal 201 └── gamma.proto 202 203.. _module-pw_protobuf_compiler-add-to-python-package: 204 205Adding Python proto modules to an existing package 206-------------------------------------------------- 207By default, generated Python proto modules are organized into their own Python 208package. These proto modules can instead be added to an existing Python package 209declared with ``pw_python_package``. This is done by setting the 210``python_package`` argument on the ``pw_proto_library`` and the 211``proto_library`` argument on the ``pw_python_package``. 212 213For example, the protos declared in ``my_protos`` will be nested in the Python 214package declared by ``my_package``. 215 216.. code-block:: 217 218 pw_proto_library("my_protos") { 219 sources = [ "hello.proto ] 220 prefix = "foo" 221 python_package = ":my_package" 222 } 223 224 pw_python_pacakge("my_package") { 225 generate_setup = { 226 metadata = { 227 name = "foo" 228 version = "1.0" 229 } 230 } 231 232 sources = [ "foo/cool_module.py" ] 233 proto_library = ":my_protos" 234 } 235 236The ``hello_pb2.py`` proto module can be used alongside other files in the 237``foo`` package. 238 239.. code-block:: python 240 241 from foo import cool_module, hello_pb2 242 243Working with externally defined protos 244-------------------------------------- 245``pw_proto_library`` targets may be used to build ``.proto`` sources from 246existing projects. In these cases, it may be necessary to supply the 247``strip_prefix`` argument, which specifies the protobuf include path to use for 248``protoc``. If only a single external protobuf is being compiled, the 249``python_module_as_package`` option can be used to override the requirement that 250the protobuf be nested under a directory. This option generates a Python package 251with the same name as the proto file, so that the generated proto can be 252imported as if it were a standalone Python module. 253 254For example, the ``pw_proto_library`` target for Nanopb sets 255``python_module_as_package`` to ``nanopb_pb2``. 256 257.. code-block:: 258 259 pw_proto_library("proto") { 260 strip_prefix = "$dir_pw_third_party_nanopb/generator/proto" 261 sources = [ "$dir_pw_third_party_nanopb/generator/proto/nanopb.proto" ] 262 python_module_as_package = "nanopb_pb2" 263 } 264 265In Python, this makes ``nanopb.proto`` available as ``import nanopb_pb2`` via 266the ``nanopb_pb2`` Python package. In C++, ``nanopb.proto`` is accessed as 267``#include "nanopb.pwpb.h"``. 268 269The ``python_module_as_package`` feature should only be used when absolutely 270necessary --- for example, to support proto files that include 271``import "nanopb.proto"``. 272 273Specifying a custom ``protoc`` 274------------------------------ 275If your build needs to use a custom build of ``protoc`` rather than the one 276supplied by pigweed it can be specified by setting 277``pw_protobuf_compiler_PROTOC_TARGET`` to a GN target that produces a ``protoc`` 278executable and ``pw_protobuf_compiler_PROTOC_BINARY`` to the path, relative to 279``root_build_dir``, of the ``protoc`` executable. 280 281For all ``protoc`` invocations, the build will add a dependency on that target 282and will invoke that executable. 283 284.. _module-pw_protobuf_compiler-cmake: 285 286CMake 287===== 288CMake provides a ``pw_proto_library`` function with similar features as the 289GN template. The CMake build only supports building firmware code, so 290``pw_proto_library`` does not generate a Python package. 291 292**Arguments** 293 294* ``NAME``: the base name of the libraries to create 295* ``SOURCES``: .proto source files 296* ``DEPS``: dependencies on other ``pw_proto_library`` targets 297* ``PREFIX``: prefix add to the proto files 298* ``STRIP_PREFIX``: prefix to remove from the proto files 299* ``INPUTS``: files to include along with the .proto files (such as Nanopb 300 .options files) 301 302**Example** 303 304.. code-block:: cmake 305 306 include($ENV{PW_ROOT}/pw_build/pigweed.cmake) 307 include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake) 308 309 pw_proto_library(my_module.my_protos 310 SOURCES 311 my_protos/foo.proto 312 my_protos/bar.proto 313 ) 314 315 pw_proto_library(my_module.my_protos 316 SOURCES 317 my_protos/foo.proto 318 my_protos/bar.proto 319 ) 320 321 pw_proto_library(my_module.my_other_protos 322 SOURCES 323 some/other/path/baz.proto # imports foo.proto 324 325 # This removes the "some/other/path" prefix from the proto files. 326 STRIP_PREFIX 327 some/other/path 328 329 # This adds the "my_other_protos/" prefix to the proto files. 330 PREFIX 331 my_other_protos 332 333 # Proto libraries depend on other proto libraries directly. 334 DEPS 335 my_module.my_protos 336 ) 337 338 add_library(my_module.my_cc_code 339 foo.cc 340 bar.cc 341 baz.cc 342 ) 343 344 # When depending on protos in a source_set, specify the generator suffix. 345 target_link_libraries(my_module.my_cc_code PUBLIC 346 my_module.my_other_protos.pwpb 347 ) 348 349These proto files are accessed in C++ the same as in the GN build: 350 351.. code-block:: cpp 352 353 #include "my_other_protos/baz.pwpb.h" 354 355**Supported Codegen** 356 357CMake supports the following compiled proto libraries via the specified 358sub-targets generated by a ``pw_proto_library``. 359 360* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code 361* ``${NAME}.pwpb_rpc`` - Generated C++ pw_protobuf pw_rpc code 362* ``${NAME}.nanopb`` - Generated C++ nanopb code (requires Nanopb) 363* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires Nanopb) 364* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library) 365 366Bazel 367===== 368In Bazel we provide a set rules with similar features to the GN templates: 369 370* ``pwpb_proto_library`` - Generated C++ pw_protobuf code 371* ``pwpb_rpc_proto_library`` - Generated C++ pw_protobuf pw_rpc code 372* ``raw_rpc_proto_library`` - Generated C++ raw pw_rpc code (no protobuf library) 373* ``nanopb_proto_library`` - Generated C++ nanopb code 374* ``nanopb_rpc_proto_library`` - Generated C++ Nanopb pw_rpc code 375 376These rules build the corresponding firmware code; there are no rules for 377generating Python libraries. The Bazel rules differ slightly compared to the GN 378build to be more in line with what would be considered idiomatic in Bazel. 379 380To use Pigweeds Protobuf rules you must first pull in the required dependencies 381into your Bazel WORKSPACE file. e.g. 382 383.. code-block:: python 384 385 # WORKSPACE ... 386 load("@pigweed//pw_protobuf_compiler:deps.bzl", "pw_protobuf_dependencies") 387 pw_protobuf_dependencies() 388 389Bazel uses a different set of rules to manage proto files than it does to 390compile them. e.g. 391 392.. code-block:: python 393 394 # BUILD ... 395 load("@rules_proto//proto:defs.bzl", "proto_library") 396 load("@pigweed//pw_protobuf_compiler:pw_proto_library.bzl", 397 "nanopb_proto_library", 398 "nanopb_rpc_proto_library", 399 "pwpb_proto_library", 400 "raw_rpc_proto_library", 401 ) 402 403 # Manages proto sources and dependencies. 404 proto_library( 405 name = "my_proto", 406 srcs = [ 407 "my_protos/foo.proto", 408 "my_protos/bar.proto", 409 ] 410 ) 411 412 # Compiles dependent protos to C++. 413 pwpb_proto_library( 414 name = "my_proto_pwpb", 415 deps = [":my_proto"], 416 ) 417 418 nanopb_proto_library( 419 name = "my_proto_nanopb", 420 deps = [":my_proto"], 421 ) 422 423 raw_rpc_proto_library( 424 name = "my_proto_raw_rpc", 425 deps = [":my_proto"], 426 ) 427 428 nanopb_rpc_proto_library( 429 name = "my_proto_nanopb_rpc", 430 nanopb_proto_library_deps = [":my_proto_nanopb"], 431 deps = [":my_proto"], 432 ) 433 434 # Library that depends on only pw_protobuf generated proto targets. 435 cc_library( 436 name = "my_proto_only_lib", 437 srcs = ["my/proto_only.cc"], 438 deps = [":my_proto_pwpb"], 439 ) 440 441 # Library that depends on only Nanopb generated proto targets. 442 cc_library( 443 name = "my_nanopb_only_lib", 444 srcs = ["my/nanopb_only.cc"], 445 deps = [":my_proto_nanopb"], 446 ) 447 448 # Library that depends on pw_protobuf and pw_rpc/raw. 449 cc_library( 450 name = "my_raw_rpc_lib", 451 srcs = ["my/raw_rpc.cc"], 452 deps = [ 453 ":my_proto_pwpb", 454 ":my_proto_raw_rpc", 455 ], 456 ) 457 cc_library( 458 name = "my_nanopb_rpc_lib", 459 srcs = ["my/proto_only.cc"], 460 deps = [ 461 ":my_proto_nanopb_rpc", 462 ], 463 ) 464 465From ``my/lib.cc`` you can now include the generated headers. 466e.g. 467 468.. code-block:: cpp 469 470 #include "my_protos/bar.pwpb.h" 471 // and/or RPC headers 472 #include "my_protos/bar.raw_rpc.pb.h 473 // or 474 #include "my_protos/bar.nanopb_rpc.pb.h" 475 476 477Why isn't there one rule to generate all the code? 478-------------------------------------------------- 479There is! Like in GN, it's called ``pw_proto_library``, and has subtargets 480corresponding to the different codegen flavors. However, new code **should not** 481use this. It is deprecated, and will be removed in the future. 482 483The ``pw_proto_library`` target has a number of disadvantages: 484 485#. As a general bazel style rule, macros should produce exactly one target for 486 external use, named according to the invocation's name argument. ``BUILD`` 487 files are easier to follow when the name specified in the macro call 488 actually matches the name of the generated target. This is not possible if a 489 single macro is generating multiple targets, as ``pw_proto_library`` does. 490#. If you depend directly on the ``pw_proto_library``, rather than the 491 appropriate subtargets, you will build code you don't actually use. You may 492 even fetch dependencies you don't need, like nanopb. 493#. The subtargets you don't depend on are still added to your BUILD files by 494 the ``pw_proto_library`` macro, and bazel will attempt to build them when 495 you run ``bazel build //...``. This may cause build breakages, and has 496 forced us to implement `awkward workarounds 497 <https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/96980>`_. 498 499---------------------- 500Python proto libraries 501---------------------- 502``pw_protobuf_compiler`` includes utilties for working with protocol buffers 503in Python. The tools facilitate using protos from their package names 504(``my.pkg.Message()``) rather than their generated module names 505(``proto_source_file_pb2.Message()``). 506 507``python_protos`` module 508======================== 509.. automodule:: pw_protobuf_compiler.python_protos 510 :members: proto_repr, Library 511