xref: /aosp_15_r20/external/pigweed/pw_protobuf_compiler/proto.gni (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2020 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14
15import("//build_overrides/pigweed.gni")
16import("//build_overrides/pigweed_environment.gni")
17
18import("$dir_pw_build/error.gni")
19import("$dir_pw_build/input_group.gni")
20import("$dir_pw_build/mirror_tree.gni")
21import("$dir_pw_build/python.gni")
22import("$dir_pw_build/python_action.gni")
23import("$dir_pw_build/python_gn_args.gni")
24import("$dir_pw_build/target_types.gni")
25import("$dir_pw_third_party/nanopb/nanopb.gni")
26import("toolchain.gni")
27
28# Variables forwarded from the public pw_proto_library template to the final
29# pw_source_set.
30_forwarded_vars = [
31  "testonly",
32  "visibility",
33]
34
35declare_args() {
36  # To override the protobuf compiler used set this to the GN target that builds
37  # the protobuf compiler.
38  pw_protobuf_compiler_PROTOC_TARGET = ""
39}
40
41if (pw_protobuf_compiler_PROTOC_TARGET == "" &&
42    defined(pw_env_setup_CIPD_PIGWEED)) {
43  _default_protc =
44      rebase_path("$pw_env_setup_CIPD_PIGWEED/bin/protoc", root_build_dir)
45  if (host_os == "win") {
46    _default_protc += ".exe"
47  }
48} else {
49  _default_protc = ""
50}
51
52declare_args() {
53  # To override the protobuf compiler used set this to the path, relative to the
54  # root_build_dir, to the protoc binary.
55  pw_protobuf_compiler_PROTOC_BINARY = _default_protc
56}
57
58# Internal template that invokes protoc with a pw_python_action. This should not
59# be used outside of this file; use pw_proto_library instead.
60#
61# This creates the internal GN target $target_name.$language._gen that compiles
62# proto files with protoc.
63template("_pw_invoke_protoc") {
64  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
65    if (defined(invoker.out_dir)) {
66      _out_dir = invoker.out_dir
67    } else {
68      _out_dir = "${invoker.base_out_dir}/${invoker.language}"
69      if (defined(invoker.module_as_package) &&
70          invoker.module_as_package != "") {
71        assert(invoker.language == "python")
72        _out_dir = "$_out_dir/${invoker.module_as_package}"
73      }
74    }
75
76    _includes =
77        rebase_path(get_target_outputs(":${invoker.base_target}._includes"),
78                    root_build_dir)
79
80    pw_python_action("$target_name._gen") {
81      script =
82          "$dir_pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py"
83
84      # NOTE: A python_dep on "$dir_pw_protobuf_compiler/py" should not be
85      # included building that Python package which requires the build venv to
86      # be created. Instead, use python_metadata_deps to only add
87      # pw_protobuf_compiler to the PYTHONPATH.
88      python_deps = []
89
90      # Add pw_protobuf_compiler and its dependencies to the PYTHONPATH when
91      # running this action.
92      python_metadata_deps = [ "$dir_pw_protobuf_compiler/py" ]
93
94      python_deps = []
95      if (defined(invoker.python_deps)) {
96        python_deps += invoker.python_deps
97      }
98
99      deps = [
100        ":${invoker.base_target}._includes",
101        ":${invoker.base_target}._sources",
102      ]
103
104      foreach(dep, invoker.deps) {
105        deps += [ get_label_info(dep, "label_no_toolchain") + "._gen" ]
106      }
107
108      if (defined(invoker.other_deps)) {
109        deps += invoker.other_deps
110      }
111
112      args = []
113      if (!pw_protobuf_compiler_GENERATE_LEGACY_ENUM_SNAKE_CASE_NAMES) {
114        args += [ "--exclude-pwpb-legacy-snake-case-field-name-enums" ]
115      }
116      if (pw_protobuf_compiler_NO_GENERIC_OPTIONS_FILES) {
117        args += [ "--pwpb-no-generic-options-files" ]
118      }
119      if (pw_protobuf_compiler_NO_ONEOF_CALLBACKS ||
120          invoker.use_legacy_oneof_interfaces) {
121        args += [ "--pwpb-no-oneof-callbacks" ]
122      }
123      if (pw_protobuf_compiler_PROTOC_TARGET != "") {
124        assert(
125            pw_protobuf_compiler_PROTOC_BINARY != "",
126            "if pw_protobuf_compiler_PROTOC_TARGET is set then pw_protobuf_compiler_PROTOC_BINARY must be set to the path to protoc, relative to the root_build_dir.")
127        _protoc_src_dir =
128            get_label_info(pw_protobuf_compiler_PROTOC_TARGET, "dir") + "/src"
129
130        args += [
131          "--protoc",
132          pw_protobuf_compiler_PROTOC_BINARY,
133          "--proto-path",
134          rebase_path(_protoc_src_dir, root_build_dir),
135        ]
136        deps += [ pw_protobuf_compiler_PROTOC_TARGET ]
137      }
138      args += [
139                "--language",
140                invoker.language,
141                "--include-file",
142                _includes[0],
143                "--compile-dir",
144                rebase_path(invoker.compile_dir, root_build_dir),
145                "--out-dir",
146                rebase_path(_out_dir, root_build_dir),
147                "--sources",
148              ] + rebase_path(invoker.sources, root_build_dir)
149
150      if (defined(invoker.plugin)) {
151        inputs = [ invoker.plugin ]
152        args +=
153            [ "--plugin-path=" + rebase_path(invoker.plugin, root_build_dir) ]
154      }
155
156      if (defined(invoker.outputs)) {
157        outputs = invoker.outputs
158      } else {
159        stamp = true
160      }
161
162      if (defined(invoker.metadata)) {
163        metadata = invoker.metadata
164      }
165    }
166
167    # Output a .json file with information about this proto library.
168    _proto_info = {
169      label = get_label_info(":${invoker.target_name}", "label_no_toolchain")
170      protoc_outputs =
171          rebase_path(get_target_outputs(":$target_name._gen"), root_build_dir)
172      root = rebase_path(_out_dir, root_build_dir)
173      package = invoker.package
174
175      nested_in_python_package = ""
176      if (defined(invoker.python_package)) {
177        nested_in_python_package =
178            get_label_info(invoker.python_package, "label_no_toolchain")
179      }
180
181      dependencies = []
182      foreach(dep, invoker.deps) {
183        dependencies +=
184            rebase_path([ get_label_info(dep, "target_gen_dir") + "/" +
185                              get_label_info(dep, "name") + ".json" ],
186                        root_build_dir)
187      }
188    }
189    write_file("$target_gen_dir/$target_name.json", _proto_info, "json")
190  } else {
191    # protoc is only ever invoked from pw_protobuf_compiler_TOOLCHAIN.
192    not_needed([ "target_name" ])
193    not_needed(invoker, "*")
194  }
195}
196
197# Generates pw_protobuf C++ code for proto files, creating a source_set of the
198# generated files. This is internal and should not be used outside of this file.
199# Use pw_proto_library instead.
200template("_pw_pwpb_rpc_proto_library") {
201  # Create a target which runs protoc configured with the pwpb_rpc plugin to
202  # generate the C++ proto RPC headers.
203  _pw_invoke_protoc(target_name) {
204    forward_variables_from(invoker, "*", _forwarded_vars)
205    language = "pwpb_rpc"
206    plugin = "$dir_pw_rpc/py/pw_rpc/plugin_pwpb.py"
207    python_deps = [ "$dir_pw_rpc/py" ]
208  }
209
210  # Create a library with the generated source files.
211  config("$target_name._include_path") {
212    include_dirs = [ "${invoker.base_out_dir}/pwpb_rpc" ]
213    visibility = [ ":*" ]
214  }
215
216  pw_source_set(target_name) {
217    forward_variables_from(invoker, _forwarded_vars)
218    public_configs = [ ":$target_name._include_path" ]
219    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
220    public_deps = [
221                    ":${invoker.base_target}.pwpb",
222                    "$dir_pw_protobuf",
223                    "$dir_pw_rpc:server",
224                    "$dir_pw_rpc/pwpb:client_api",
225                    "$dir_pw_rpc/pwpb:server_api",
226                  ] + invoker.deps
227    public = invoker.outputs
228    check_includes = false
229  }
230}
231
232template("_pw_pwpb_proto_library") {
233  _pw_invoke_protoc(target_name) {
234    forward_variables_from(invoker, "*", _forwarded_vars)
235    language = "pwpb"
236    plugin = "$dir_pw_protobuf/py/pw_protobuf/plugin.py"
237    python_deps = [ "$dir_pw_protobuf/py" ]
238  }
239
240  # Create a library with the generated source files.
241  config("$target_name._include_path") {
242    include_dirs = [ "${invoker.base_out_dir}/pwpb" ]
243    visibility = [ ":*" ]
244  }
245
246  pw_source_set(target_name) {
247    forward_variables_from(invoker, _forwarded_vars)
248    public_configs = [ ":$target_name._include_path" ]
249    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
250    public_deps = [
251                    "$dir_pw_containers:vector",
252                    "$dir_pw_string:string",
253                    dir_pw_assert,
254                    dir_pw_function,
255                    dir_pw_preprocessor,
256                    dir_pw_protobuf,
257                    dir_pw_result,
258                    dir_pw_status,
259                  ] + invoker.deps
260    sources = invoker.outputs
261    public = filter_include(sources, [ "*.pwpb.h" ])
262  }
263}
264
265# Generates nanopb RPC code for proto files, creating a source_set of the
266# generated files. This is internal and should not be used outside of this file.
267# Use pw_proto_library instead.
268template("_pw_nanopb_rpc_proto_library") {
269  # Create a target which runs protoc configured with the nanopb_rpc plugin to
270  # generate the C++ proto RPC headers.
271  _pw_invoke_protoc(target_name) {
272    forward_variables_from(invoker, "*", _forwarded_vars)
273    language = "nanopb_rpc"
274    plugin = "$dir_pw_rpc/py/pw_rpc/plugin_nanopb.py"
275    python_deps = [ "$dir_pw_rpc/py" ]
276  }
277
278  # Create a library with the generated source files.
279  config("$target_name._include_path") {
280    include_dirs = [ "${invoker.base_out_dir}/nanopb_rpc" ]
281    visibility = [ ":*" ]
282  }
283
284  pw_source_set(target_name) {
285    forward_variables_from(invoker, _forwarded_vars)
286    public_configs = [ ":$target_name._include_path" ]
287    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
288    public_deps = [
289                    ":${invoker.base_target}.nanopb",
290                    "$dir_pw_rpc:server",
291                    "$dir_pw_rpc/nanopb:client_api",
292                    "$dir_pw_rpc/nanopb:server_api",
293                    "$dir_pw_third_party/nanopb",
294                  ] + invoker.deps
295    public = invoker.outputs
296    check_includes = false
297  }
298}
299
300# Generates nanopb code for proto files, creating a source_set of the generated
301# files. This is internal and should not be used outside of this file. Use
302# pw_proto_library instead.
303template("_pw_nanopb_proto_library") {
304  # When compiling with the Nanopb plugin, the nanopb.proto file is already
305  # compiled internally, so skip recompiling it with protoc.
306  if (rebase_path(invoker.sources, invoker.compile_dir) == [ "nanopb.proto" ]) {
307    group("$target_name._gen") {
308      deps = [
309        ":${invoker.base_target}._sources($pw_protobuf_compiler_TOOLCHAIN)",
310      ]
311    }
312
313    group("$target_name") {
314      deps = invoker.deps +
315             [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
316    }
317  } else {
318    # Create a target which runs protoc configured with the nanopb plugin to
319    # generate the C proto sources.
320    _pw_invoke_protoc(target_name) {
321      forward_variables_from(invoker, "*", _forwarded_vars)
322      language = "nanopb"
323      plugin = "$dir_pw_third_party_nanopb/generator/protoc-gen-nanopb"
324      other_deps = [ "$dir_pw_third_party/nanopb:generate_nanopb_proto.action" ]
325    }
326
327    # Create a library with the generated source files.
328    config("$target_name._include_path") {
329      include_dirs = [ "${invoker.base_out_dir}/nanopb" ]
330      visibility = [ ":*" ]
331    }
332
333    pw_source_set(target_name) {
334      forward_variables_from(invoker, _forwarded_vars)
335      public_configs = [ ":$target_name._include_path" ]
336      deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
337      public_deps = [ "$dir_pw_third_party/nanopb" ] + invoker.deps
338      sources = invoker.outputs
339      public = filter_include(sources, [ "*.pb.h" ])
340    }
341  }
342}
343
344# Generates raw RPC code for proto files, creating a source_set of the generated
345# files. This is internal and should not be used outside of this file. Use
346# pw_proto_library instead.
347template("_pw_raw_rpc_proto_library") {
348  # Create a target which runs protoc configured with the nanopb_rpc plugin to
349  # generate the C++ proto RPC headers.
350  _pw_invoke_protoc(target_name) {
351    forward_variables_from(invoker, "*", _forwarded_vars)
352    language = "raw_rpc"
353    plugin = "$dir_pw_rpc/py/pw_rpc/plugin_raw.py"
354    python_deps = [ "$dir_pw_rpc/py" ]
355  }
356
357  # Create a library with the generated source files.
358  config("$target_name._include_path") {
359    include_dirs = [ "${invoker.base_out_dir}/raw_rpc" ]
360    visibility = [ ":*" ]
361  }
362
363  pw_source_set(target_name) {
364    forward_variables_from(invoker, _forwarded_vars)
365    public_configs = [ ":$target_name._include_path" ]
366    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
367    public_deps = [
368                    "$dir_pw_rpc:server",
369                    "$dir_pw_rpc/raw:client_api",
370                    "$dir_pw_rpc/raw:server_api",
371                  ] + invoker.deps
372    public = invoker.outputs
373    check_includes = false
374  }
375}
376
377# Generates Go code for proto files, listing the proto output directory in the
378# metadata variable GOPATH. Internal use only.
379template("_pw_go_proto_library") {
380  _proto_gopath = "$root_gen_dir/go"
381
382  _pw_invoke_protoc(target_name) {
383    forward_variables_from(invoker, "*")
384    language = "go"
385    metadata = {
386      gopath = [ "GOPATH+=" + rebase_path(_proto_gopath) ]
387      external_deps = [
388        "github.com/golang/protobuf/proto",
389        "google.golang.org/grpc",
390      ]
391    }
392
393    # Override the default "$base_out_dir/$language" output path.
394    out_dir = "$_proto_gopath/src"
395  }
396
397  group(target_name) {
398    deps =
399        invoker.deps + [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
400  }
401}
402
403# Generates Python code for proto files, creating a pw_python_package containing
404# the generated files. This is internal and should not be used outside of this
405# file. Use pw_proto_library instead.
406template("_pw_python_proto_library") {
407  _pw_invoke_protoc(target_name) {
408    forward_variables_from(invoker, "*", _forwarded_vars + [ "python_package" ])
409    language = "python"
410
411    if (defined(invoker.python_package)) {
412      python_package = invoker.python_package
413    }
414  }
415
416  if (defined(invoker.python_package) && invoker.python_package != "") {
417    # This package is nested in another Python package. Depending on this
418    # its python subtarget is equivalent to depending on the Python package it
419    # is nested in.
420    pw_python_group(target_name) {
421      python_deps = [ invoker.python_package ]
422    }
423
424    # This proto library is merged into another package, but create a target to
425    # collect its dependencies that the other package can depend on.
426    pw_python_group(target_name + "._deps") {
427      python_deps = invoker.deps
428      other_deps =
429          [ ":${invoker.target_name}._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
430    }
431  } else {
432    # Create a Python package with the generated source files.
433    pw_python_package(target_name) {
434      forward_variables_from(invoker, _forwarded_vars)
435      _target_dir =
436          get_path_info(get_label_info(":${invoker.base_target}", "dir"),
437                        "abspath")
438      generate_setup = {
439        metadata = {
440          # Default to a package name that include the full source path to avoid
441          # conflicts with other packages when collecting all the .whl files
442          # with pw_python_wheels().
443          name =
444              string_replace(string_replace(_target_dir, "//", ""), "/", "_") +
445              "_" + invoker.base_target
446
447          # The package name should match where the __init__.py lives. If
448          # module_as_package is specified use that for the Python package name.
449          if (defined(invoker.module_as_package) &&
450              invoker.module_as_package != "") {
451            name = invoker.module_as_package
452          }
453          version =
454              "0.0.1"  # TODO(hepler): Need to be able to set this verison.
455        }
456      }
457      sources = invoker.outputs
458      strip_prefix = "${invoker.base_out_dir}/python"
459      python_deps = invoker.deps
460      other_deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
461      static_analysis = []
462
463      _pw_module_as_package = invoker.module_as_package != ""
464    }
465  }
466}
467
468# Generates protobuf code from .proto definitions for various languages.
469# For each supported generator, creates a sub-target named:
470#
471#   <target_name>.<generator>
472#
473# GN permits using abbreviated labels when the target name matches the directory
474# name (e.g. //foo for //foo:foo). For consistency with this, the sub-targets
475# for each generator are aliased to the directory when the target name is the
476# same. For example, these two labels are equivalent:
477#
478#   //path/to/my_protos:my_protos.pwpb
479#   //path/to/my_protos:pwpb
480#
481# pw_protobuf_library targets generate Python packages. As such, they must have
482# globally unique package names. The first directory of the prefix or the first
483# common directory of the sources is used as the Python package.
484#
485# Args:
486#   sources: List of input .proto files.
487#   deps: List of other pw_proto_library dependencies.
488#   other_deps: List of other non-proto dependencies.
489#   inputs: Other files on which the protos depend (e.g. nanopb .options files).
490#   prefix: A prefix to add to the source protos prior to compilation. For
491#       example, a source called "foo.proto" with prefix = "nested" will be
492#       compiled with protoc as "nested/foo.proto".
493#   strip_prefix: Remove this prefix from the source protos. All source and
494#       input files must be nested under this path.
495#   python_package: Label of Python package to which to add the proto modules.
496#       The .python subtarget will redirect to this package.
497#
498template("pw_proto_library") {
499  assert(defined(invoker.sources) && invoker.sources != [],
500         "pw_proto_library requires .proto source files")
501
502  if (defined(invoker.python_module_as_package)) {
503    _module_as_package = invoker.python_module_as_package
504
505    _must_be_one_source = invoker.sources
506    assert([ _must_be_one_source[0] ] == _must_be_one_source,
507           "'python_module_as_package' requires exactly one source file")
508    assert(_module_as_package != "",
509           "'python_module_as_package' cannot be be empty")
510    assert(string_split(_module_as_package, "/") == [ _module_as_package ],
511           "'python_module_as_package' cannot contain slashes")
512    assert(!defined(invoker.prefix),
513           "'prefix' cannot be provided with 'python_module_as_package'")
514  } else {
515    _module_as_package = ""
516  }
517
518  if (defined(invoker.strip_prefix)) {
519    _source_root = get_path_info(invoker.strip_prefix, "abspath")
520  } else {
521    _source_root = get_path_info(".", "abspath")
522  }
523
524  if (defined(invoker.prefix)) {
525    _prefix = invoker.prefix
526  } else {
527    _prefix = ""
528  }
529
530  _root_dir_name = ""
531  _source_names = []
532
533  # Determine the Python package name to use for these protos. If there is no
534  # prefix, the first directory the sources are nested under is used.
535  foreach(source, rebase_path(invoker.sources, _source_root)) {
536    _path_components = []
537    _path_components = string_split(source, "/")
538
539    if (_root_dir_name == "") {
540      _root_dir_name = _path_components[0]
541    } else {
542      assert(_prefix != "" || _path_components[0] == _root_dir_name,
543             "Unless 'prefix' is supplied, all .proto sources in a " +
544                 "pw_proto_library must be in the same directory tree")
545    }
546
547    _source_names +=
548        [ get_path_info(source, "dir") + "/" + get_path_info(source, "name") ]
549  }
550
551  # If the 'prefix' was supplied, use that for the package directory.
552  if (_prefix != "") {
553    _prefix_path_components = string_split(_prefix, "/")
554    _root_dir_name = _prefix_path_components[0]
555  }
556
557  assert(
558      _root_dir_name != "" && _root_dir_name != "." && _root_dir_name != "..",
559      "Either a 'prefix' must be specified or all sources must be nested " +
560          "under a common directory")
561
562  if (defined(invoker.deps)) {
563    _deps = invoker.deps
564  } else {
565    _deps = []
566  }
567
568  _common = {
569    base_target = target_name
570
571    # This is the output directory for all files related to this proto library.
572    # Sources are mirrored to "$base_out_dir/sources" and protoc puts outputs in
573    # "$base_out_dir/$language" by default.
574    base_out_dir =
575        get_label_info(":$target_name($pw_protobuf_compiler_TOOLCHAIN)",
576                       "target_gen_dir") + "/$target_name.proto_library"
577
578    compile_dir = "$base_out_dir/sources"
579
580    # Refer to the source files as the are mirrored to the output directory.
581    sources = []
582    foreach(file, rebase_path(invoker.sources, _source_root)) {
583      sources += [ "$compile_dir/$_prefix/$file" ]
584    }
585
586    package = _root_dir_name
587
588    use_legacy_oneof_interfaces = false
589    if (defined(invoker.use_legacy_oneof_interfaces)) {
590      use_legacy_oneof_interfaces = invoker.use_legacy_oneof_interfaces
591    }
592  }
593
594  # For each proto target, create a file which collects the base directories of
595  # all of its dependencies to list as include paths to protoc.
596  generated_file("$target_name._includes") {
597    # Collect metadata from the include path files of each dependency.
598
599    deps = []
600    foreach(dep, _deps) {
601      _base = get_label_info(dep, "label_no_toolchain")
602      deps += [ "$_base._includes(" + get_label_info(dep, "toolchain") + ")" ]
603    }
604
605    data_keys = [ "protoc_includes" ]
606    outputs = [ "${_common.base_out_dir}/includes.txt" ]
607
608    # Indicate this library's base directory for its dependents.
609    metadata = {
610      protoc_includes = [ rebase_path(_common.compile_dir, root_build_dir) ]
611    }
612  }
613
614  # Mirror the proto sources to the output directory with the prefix added.
615  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
616    pw_mirror_tree("$target_name._sources") {
617      source_root = _source_root
618      sources = invoker.sources
619      if (defined(invoker.other_deps)) {
620        deps = invoker.other_deps
621      }
622
623      if (defined(invoker.inputs)) {
624        sources += invoker.inputs
625      }
626
627      directory = "${_common.compile_dir}/$_prefix"
628    }
629  } else {
630    not_needed(invoker,
631               [
632                 "inputs",
633                 "other_deps",
634               ])
635  }
636
637  # Enumerate all of the protobuf generator targets.
638
639  _pw_pwpb_rpc_proto_library("$target_name.pwpb_rpc") {
640    forward_variables_from(invoker, _forwarded_vars)
641    forward_variables_from(_common, "*")
642
643    deps = []
644    foreach(dep, _deps) {
645      _base = get_label_info(dep, "label_no_toolchain")
646      deps += [ "$_base.pwpb_rpc(" + get_label_info(dep, "toolchain") + ")" ]
647    }
648
649    outputs = []
650    foreach(name, _source_names) {
651      outputs += [ "$base_out_dir/pwpb_rpc/$_prefix/${name}.rpc.pwpb.h" ]
652    }
653  }
654
655  _pw_pwpb_proto_library("$target_name.pwpb") {
656    forward_variables_from(invoker, _forwarded_vars)
657    forward_variables_from(_common, "*")
658
659    deps = []
660    foreach(dep, _deps) {
661      _base = get_label_info(dep, "label_no_toolchain")
662      deps += [ "$_base.pwpb(" + get_label_info(dep, "toolchain") + ")" ]
663    }
664
665    outputs = []
666    foreach(name, _source_names) {
667      outputs += [ "$base_out_dir/pwpb/$_prefix/${name}.pwpb.h" ]
668    }
669  }
670
671  if (dir_pw_third_party_nanopb != "") {
672    _pw_nanopb_rpc_proto_library("$target_name.nanopb_rpc") {
673      forward_variables_from(invoker, _forwarded_vars)
674      forward_variables_from(_common, "*")
675
676      deps = []
677      foreach(dep, _deps) {
678        _lbl = get_label_info(dep, "label_no_toolchain")
679        deps += [ "$_lbl.nanopb_rpc(" + get_label_info(dep, "toolchain") + ")" ]
680      }
681
682      outputs = []
683      foreach(name, _source_names) {
684        outputs += [ "$base_out_dir/nanopb_rpc/$_prefix/${name}.rpc.pb.h" ]
685      }
686    }
687
688    _pw_nanopb_proto_library("$target_name.nanopb") {
689      forward_variables_from(invoker, _forwarded_vars)
690      forward_variables_from(_common, "*")
691
692      deps = []
693      foreach(dep, _deps) {
694        _base = get_label_info(dep, "label_no_toolchain")
695        deps += [ "$_base.nanopb(" + get_label_info(dep, "toolchain") + ")" ]
696      }
697
698      outputs = []
699      foreach(name, _source_names) {
700        outputs += [
701          "$base_out_dir/nanopb/$_prefix/${name}.pb.h",
702          "$base_out_dir/nanopb/$_prefix/${name}.pb.c",
703        ]
704      }
705    }
706  } else {
707    pw_error("$target_name.nanopb_rpc") {
708      message =
709          "\$dir_pw_third_party_nanopb must be set to generate nanopb RPC code."
710    }
711
712    pw_error("$target_name.nanopb") {
713      message =
714          "\$dir_pw_third_party_nanopb must be set to compile nanopb protobufs."
715    }
716  }
717
718  _pw_raw_rpc_proto_library("$target_name.raw_rpc") {
719    forward_variables_from(invoker, _forwarded_vars)
720    forward_variables_from(_common, "*")
721
722    deps = []
723    foreach(dep, _deps) {
724      _base = get_label_info(dep, "label_no_toolchain")
725      deps += [ "$_base.raw_rpc(" + get_label_info(dep, "toolchain") + ")" ]
726    }
727
728    outputs = []
729    foreach(name, _source_names) {
730      outputs += [ "$base_out_dir/raw_rpc/$_prefix/${name}.raw_rpc.pb.h" ]
731    }
732  }
733
734  _pw_go_proto_library("$target_name.go") {
735    sources = _common.sources
736
737    deps = []
738    foreach(dep, _deps) {
739      _base = get_label_info(dep, "label_no_toolchain")
740      deps += [ "$_base.go(" + get_label_info(dep, "toolchain") + ")" ]
741    }
742
743    forward_variables_from(_common, "*")
744  }
745
746  _pw_python_proto_library("$target_name.python") {
747    forward_variables_from(_common, "*")
748    forward_variables_from(invoker, [ "python_package" ])
749    module_as_package = _module_as_package
750
751    deps = []
752    foreach(dep, _deps) {
753      _base = get_label_info(dep, "label_no_toolchain")
754      deps += [ "$_base.python(" + get_label_info(dep, "toolchain") + ")" ]
755    }
756
757    if (module_as_package == "") {
758      _python_prefix = "$base_out_dir/python/$_prefix"
759    } else {
760      _python_prefix = "$base_out_dir/python/$module_as_package"
761    }
762
763    outputs = []
764    foreach(name, _source_names) {
765      outputs += [
766        "$_python_prefix/${name}_pb2.py",
767        "$_python_prefix/${name}_pb2.pyi",
768      ]
769    }
770  }
771
772  # All supported pw_protobuf generators.
773  _protobuf_generators = [
774    "pwpb",
775    "pwpb_rpc",
776    "nanopb",
777    "nanopb_rpc",
778    "raw_rpc",
779    "go",
780    "python",
781  ]
782
783  # If the label matches the directory name, alias the subtargets to the
784  # directory (e.g. //foo:nanopb is an alias for //foo:foo.nanopb).
785  if (get_label_info(":$target_name", "name") ==
786      get_path_info(get_label_info(":$target_name", "dir"), "name")) {
787    foreach(_generator, _protobuf_generators - [ "python" ]) {
788      group(_generator) {
789        public_deps = [ ":${invoker.target_name}.$_generator" ]
790      }
791    }
792
793    pw_python_group("python") {
794      python_deps = [ ":${invoker.target_name}.python" ]
795    }
796  }
797
798  # If the user attempts to use the target directly instead of one of the
799  # generator targets, run a script which prints a nice error message.
800  pw_python_action(target_name) {
801    script = string_join("/",
802                         [
803                           dir_pw_protobuf_compiler,
804                           "py",
805                           "pw_protobuf_compiler",
806                           "proto_target_invalid.py",
807                         ])
808    args = [
809             "--target",
810             target_name,
811             "--dir",
812             get_path_info(".", "abspath"),
813             "--root",
814             "//",
815           ] + _protobuf_generators
816    stamp = true
817  }
818}
819