• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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