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 9Generator support 10================= 11Protobuf code generation is currently supported for the following generators: 12 13+-------------+----------------+-----------------------------------------------+ 14| Generator | Code | Notes | 15+-------------+----------------+-----------------------------------------------+ 16| pw_protobuf | ``pwpb`` | Compiles using ``pw_protobuf``. | 17+-------------+----------------+-----------------------------------------------+ 18| Nanopb | ``nanopb`` | Compiles using Nanopb. The build argument | 19| | | ``dir_pw_third_party_nanopb`` must be set to | 20| | | point to a local nanopb installation. | 21+-------------+----------------+-----------------------------------------------+ 22| Nanopb RPC | ``nanopb_rpc`` | Compiles pw_rpc service and client code for | 23| | | nanopb. Requires a nanopb installation. | 24+-------------+----------------+-----------------------------------------------+ 25| Raw RPC | ``raw_rpc`` | Compiles raw binary pw_rpc service code. | 26+-------------+----------------+-----------------------------------------------+ 27| Go | ``go`` | Compiles using the standard Go protobuf | 28| | | plugin with gRPC service support. | 29+-------------+----------------+-----------------------------------------------+ 30| Python | ``python`` | Compiles using the standard Python protobuf | 31| | | plugin, creating a ``pw_python_package``. | 32+-------------+----------------+-----------------------------------------------+ 33| Typescript | ``typescript`` | Compilation is supported in Bazel via | 34| | | @rules_proto_grpc. ProtoCollection provides | 35| | | convience methods for proto descriptors. | 36+-------------+----------------+-----------------------------------------------+ 37 38GN template 39=========== 40This module provides a ``pw_proto_library`` GN template that defines a 41collection of protobuf files that should be compiled together. The template 42creates a sub-target for each supported generator, named 43``<target_name>.<generator>``. These sub-targets generate their respective 44protobuf code, and expose it to the build system appropriately (e.g. a 45``pw_source_set`` for C/C++). 46 47For example, given the following target: 48 49.. code-block:: 50 51 pw_proto_library("test_protos") { 52 sources = [ "my_test_protos/test.proto" ] 53 } 54 55``test_protos.pwpb`` compiles code for pw_protobuf, and ``test_protos.nanopb`` 56compiles using Nanopb (if it's installed). 57 58Protobuf code is only generated when a generator sub-target is listed as a 59dependency of another GN target. 60 61GN permits using abbreviated labels when the target name matches the directory 62name (e.g. ``//foo`` for ``//foo:foo``). For consistency with this, the 63sub-targets for each generator are aliased to the directory when the target name 64is the same. For example, these two labels are equivalent: 65 66.. code-block:: 67 68 //path/to/my_protos:my_protos.pwpb 69 //path/to/my_protos:pwpb 70 71``pw_python_package`` subtargets are also available on the ``python`` subtarget: 72 73.. code-block:: 74 75 //path/to/my_protos:my_protos.python.lint 76 //path/to/my_protos:python.lint 77 78**Supported Codegen** 79 80GN supports the following compiled proto libraries via the specified 81sub-targets generated by a ``pw_proto_library``. 82 83* ``${target_name}.pwpb`` - Generated C++ pw_protobuf code 84* ``${target_name}.nanopb`` - Generated C++ nanopb code (requires Nanopb) 85* ``${target_name}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires 86 Nanopb) 87* ``${target_name}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf 88 library) 89* ``${target_name}.go`` - Generated GO protobuf libraries 90* ``${target_name}.python`` - Generated Python protobuf libraries 91 92**Arguments** 93 94* ``sources``: List of input .proto files. 95* ``deps``: List of other pw_proto_library dependencies. 96* ``inputs``: Other files on which the protos depend (e.g. nanopb ``.options`` 97 files). 98* ``prefix``: A prefix to add to the source protos prior to compilation. For 99 example, a source called ``"foo.proto"`` with ``prefix = "nested"`` will be 100 compiled with protoc as ``"nested/foo.proto"``. 101* ``strip_prefix``: Remove this prefix from the source protos. All source and 102 input files must be nested under this path. 103* ``python_package``: Label of Python package to which to add the proto modules. 104 The .python subtarget will redirect to this package. 105 106**Example** 107 108.. code-block:: 109 110 import("$dir_pw_protobuf_compiler/proto.gni") 111 112 pw_proto_library("my_protos") { 113 sources = [ 114 "my_protos/foo.proto", 115 "my_protos/bar.proto", 116 ] 117 } 118 119 pw_proto_library("my_other_protos") { 120 sources = [ "some/other/path/baz.proto" ] # imports foo.proto 121 122 # This removes the "some/other/path" prefix from the proto files. 123 strip_prefix = "some/other/path" 124 125 # This adds the "my_other_protos/" prefix to the proto files. 126 prefix = "my_other_protos" 127 128 # Proto libraries depend on other proto libraries directly. 129 deps = [ ":my_protos" ] 130 } 131 132 source_set("my_cc_code") { 133 sources = [ 134 "foo.cc", 135 "bar.cc", 136 "baz.cc", 137 ] 138 139 # When depending on protos in a source_set, specify the generator suffix. 140 deps = [ ":my_other_protos.pwpb" ] 141 } 142 143From C++, ``baz.proto`` included as follows: 144 145.. code-block:: cpp 146 147 #include "my_other_protos/baz.pwpb.h" 148 149From Python, ``baz.proto`` is imported as follows: 150 151.. code-block:: python 152 153 from my_other_protos import baz_pb2 154 155Proto file structure 156-------------------- 157Protobuf source files must be nested under another directory when they are 158compiled. This ensures that they can be packaged properly in Python. The first 159directory is used as the Python package name, so must be unique across the 160build. The ``prefix`` option may be used to set this directory. 161 162Using ``prefix`` and ``strip_prefix`` together allows remapping proto files to 163a completely different path. This can be useful when working with protos defined 164in external libraries. For example, consider this proto library: 165 166.. code-block:: 167 168 pw_proto_library("external_protos") { 169 sources = [ 170 "//other/external/some_library/src/protos/alpha.proto", 171 "//other/external/some_library/src/protos/beta.proto, 172 "//other/external/some_library/src/protos/internal/gamma.proto", 173 ] 174 strip_prefix = "//other/external/some_library/src/protos" 175 prefix = "some_library" 176 } 177 178These protos will be compiled by protoc as if they were in this file structure: 179 180.. code-block:: 181 182 some_library/ 183 ├── alpha.proto 184 ├── beta.proto 185 └── internal 186 └── gamma.proto 187 188.. _module-pw_protobuf_compiler-add-to-python-package: 189 190Adding Python proto modules to an existing package 191-------------------------------------------------- 192By default, generated Python proto modules are organized into their own Python 193package. These proto modules can instead be added to an existing Python package 194declared with ``pw_python_package``. This is done by setting the 195``python_package`` argument on the ``pw_proto_library`` and the 196``proto_library`` argument on the ``pw_python_package``. 197 198For example, the protos declared in ``my_protos`` will be nested in the Python 199package declared by ``my_package``. 200 201.. code-block:: 202 203 pw_proto_library("my_protos") { 204 sources = [ "hello.proto ] 205 prefix = "foo" 206 python_package = ":my_package" 207 } 208 209 pw_python_pacakge("my_package") { 210 generate_setup = { 211 metadata = { 212 name = "foo" 213 version = "1.0" 214 } 215 } 216 217 sources = [ "foo/cool_module.py" ] 218 proto_library = ":my_protos" 219 } 220 221The ``hello_pb2.py`` proto module can be used alongside other files in the 222``foo`` package. 223 224.. code-block:: python 225 226 from foo import cool_module, hello_pb2 227 228Working with externally defined protos 229-------------------------------------- 230``pw_proto_library`` targets may be used to build ``.proto`` sources from 231existing projects. In these cases, it may be necessary to supply the 232``strip_prefix`` argument, which specifies the protobuf include path to use for 233``protoc``. If only a single external protobuf is being compiled, the 234``python_module_as_package`` option can be used to override the requirement that 235the protobuf be nested under a directory. This option generates a Python package 236with the same name as the proto file, so that the generated proto can be 237imported as if it were a standalone Python module. 238 239For example, the ``pw_proto_library`` target for Nanopb sets 240``python_module_as_package`` to ``nanopb_pb2``. 241 242.. code-block:: 243 244 pw_proto_library("proto") { 245 strip_prefix = "$dir_pw_third_party_nanopb/generator/proto" 246 sources = [ "$dir_pw_third_party_nanopb/generator/proto/nanopb.proto" ] 247 python_module_as_package = "nanopb_pb2" 248 } 249 250In Python, this makes ``nanopb.proto`` available as ``import nanopb_pb2`` via 251the ``nanopb_pb2`` Python package. In C++, ``nanopb.proto`` is accessed as 252``#include "nanopb.pwpb.h"``. 253 254The ``python_module_as_package`` feature should only be used when absolutely 255necessary --- for example, to support proto files that include 256``import "nanopb.proto"``. 257 258CMake 259===== 260CMake provides a ``pw_proto_library`` function with similar features as the 261GN template. The CMake build only supports building firmware code, so 262``pw_proto_library`` does not generate a Python package. 263 264**Arguments** 265 266* ``NAME``: the base name of the libraries to create 267* ``SOURCES``: .proto source files 268* ``DEPS``: dependencies on other ``pw_proto_library`` targets 269* ``PREFIX``: prefix add to the proto files 270* ``STRIP_PREFIX``: prefix to remove from the proto files 271* ``INPUTS``: files to include along with the .proto files (such as Nanopb 272 .options files) 273 274**Example** 275 276 .. code-block:: cmake 277 278 include($ENV{PW_ROOT}/pw_build/pigweed.cmake) 279 include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake) 280 281 pw_proto_library(my_module.my_protos 282 SOURCES 283 my_protos/foo.proto 284 my_protos/bar.proto 285 ) 286 287 pw_proto_library(my_module.my_protos 288 SOURCES 289 my_protos/foo.proto 290 my_protos/bar.proto 291 ) 292 293 pw_proto_library(my_module.my_other_protos 294 SOURCES 295 some/other/path/baz.proto # imports foo.proto 296 297 # This removes the "some/other/path" prefix from the proto files. 298 STRIP_PREFIX 299 some/other/path 300 301 # This adds the "my_other_protos/" prefix to the proto files. 302 PREFIX 303 my_other_protos 304 305 # Proto libraries depend on other proto libraries directly. 306 DEPS 307 my_module.my_protos 308 ) 309 310 add_library(my_module.my_cc_code 311 foo.cc 312 bar.cc 313 baz.cc 314 ) 315 316 # When depending on protos in a source_set, specify the generator suffix. 317 target_link_libraries(my_module.my_cc_code PUBLIC 318 my_module.my_other_protos.pwpb 319 ) 320 321These proto files are accessed in C++ the same as in the GN build: 322 323.. code-block:: cpp 324 325 #include "my_other_protos/baz.pwpb.h" 326 327**Supported Codegen** 328 329CMake supports the following compiled proto libraries via the specified 330sub-targets generated by a ``pw_proto_library``. 331 332* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code 333* ``${NAME}.nanopb`` - Generated C++ nanopb code (requires Nanopb) 334* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires Nanopb) 335* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library) 336 337Bazel 338===== 339Bazel provides a ``pw_proto_library`` rule with similar features as the 340GN template. The Bazel build only supports building firmware code, so 341``pw_proto_library`` does not generate a Python package. The Bazel rules differ 342slightly compared to the GN build to be more in line with what would be 343considered idiomatic in Bazel. 344 345To use Pigweeds Protobuf rules you must first pull in the required dependencies 346into your Bazel WORKSPACE file. e.g. 347 348.. code-block:: python 349 350 # WORKSPACE ... 351 load("@pigweed//pw_protobuf_compiler:deps.bzl", "pw_protobuf_dependencies") 352 pw_protobuf_dependencies() 353 354Bazel uses a different set of rules to manage proto files than it does to 355compile them. e.g. 356 357.. code-block:: python 358 359 # BUILD ... 360 load("@rules_proto//proto:defs.bzl", "proto_library") 361 load("@pigweed//pw_protobuf_compiler:proto.bzl", "pw_proto_library") 362 363 # Manages proto sources and dependencies. 364 proto_library( 365 name = "my_proto", 366 srcs = [ 367 "my_protos/foo.proto", 368 "my_protos/bar.proto", 369 ] 370 ) 371 372 # Compiles dependant protos to C++. 373 pw_proto_library( 374 name = "my_cc_proto", 375 deps = [":my_proto"], 376 ) 377 378 # Library that depends on only pw_protobuf generated proto targets. 379 pw_cc_library( 380 name = "my_proto_only_lib", 381 srcs = ["my/proto_only.cc"], 382 deps = [":my_cc_proto.pwpb"], 383 ) 384 385 # Library that depends on only Nanopb generated proto targets. 386 pw_cc_library( 387 name = "my_nanopb_only_lib", 388 srcs = ["my/nanopb_only.cc"], 389 deps = [":my_cc_proto.nanopb"], 390 ) 391 392 # Library that depends on pw_protobuf and pw_rpc/raw. 393 pw_cc_library( 394 name = "my_raw_rpc_lib", 395 srcs = ["my/raw_rpc.cc"], 396 deps = [ 397 ":my_cc_proto.pwpb", 398 ":my_cc_proto.raw_rpc", 399 ], 400 ) 401 pw_cc_library( 402 name = "my_nanopb_rpc_lib", 403 srcs = ["my/proto_only.cc"], 404 deps = [ 405 ":my_cc_proto.nanopb_rpc", 406 ], 407 ) 408 409 410 # Library that depends on generated proto targets. Prefer to depend only on 411 # those generated targets ("my_lib.pwpb", "my_lib.nanopb") that are actually 412 # required. Note that the .nanopb target may not compile for some proto 413 # messages, e.g. self-referring messages; 414 # see https://github.com/nanopb/nanopb/issues/433. 415 pw_cc_library( 416 name = "my_lib", 417 srcs = ["my/lib.cc"], 418 # This target depends on all generated proto targets 419 # e.g. name.{pwpb, nanopb, raw_rpc, nanopb_rpc} 420 deps = [":my_cc_proto"], 421 ) 422 423 424From ``my/lib.cc`` you can now include the generated headers. 425e.g. 426 427.. code:: cpp 428 429 #include "my_protos/bar.pwpb.h" 430 // and/or RPC headers 431 #include "my_protos/bar.raw_rpc.pb.h 432 // or 433 #include "my_protos/bar.nanopb_rpc.pb.h" 434 435 436 437**Supported Codegen** 438 439Bazel supports the following compiled proto libraries via the specified 440sub-targets generated by a ``pw_proto_library``. 441 442* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code 443* ``${NAME}.nanopb`` - Generated C++ nanopb code 444* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library) 445* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code 446 447 448