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