xref: /aosp_15_r20/external/pigweed/pw_protobuf_compiler/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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