• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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")
16
17import("$dir_pw_build/error.gni")
18import("$dir_pw_build/input_group.gni")
19import("$dir_pw_build/mirror_tree.gni")
20import("$dir_pw_build/python.gni")
21import("$dir_pw_build/python_action.gni")
22import("$dir_pw_build/target_types.gni")
23import("$dir_pw_third_party/nanopb/nanopb.gni")
24import("toolchain.gni")
25
26# Variables forwarded from the public pw_proto_library template to the final
27# pw_source_set.
28_forwarded_vars = [
29  "testonly",
30  "visibility",
31]
32
33# Internal template that invokes protoc with a pw_python_action. This should not
34# be used outside of this file; use pw_proto_library instead.
35#
36# This creates the internal GN target $target_name.$language._gen that compiles
37# proto files with protoc.
38template("_pw_invoke_protoc") {
39  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
40    if (defined(invoker.out_dir)) {
41      _out_dir = invoker.out_dir
42    } else {
43      _out_dir = "${invoker.base_out_dir}/${invoker.language}"
44      if (defined(invoker.module_as_package) &&
45          invoker.module_as_package != "") {
46        assert(invoker.language == "python")
47        _out_dir = "$_out_dir/${invoker.module_as_package}"
48      }
49    }
50
51    _includes =
52        rebase_path(get_target_outputs(":${invoker.base_target}._includes"),
53                    root_build_dir)
54
55    pw_python_action("$target_name._gen") {
56      script =
57          "$dir_pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py"
58
59      python_deps = [ "$dir_pw_protobuf_compiler/py" ]
60      if (defined(invoker.python_deps)) {
61        python_deps += invoker.python_deps
62      }
63
64      deps = [
65        ":${invoker.base_target}._includes",
66        ":${invoker.base_target}._sources",
67      ]
68
69      foreach(dep, invoker.deps) {
70        deps += [ get_label_info(dep, "label_no_toolchain") + "._gen" ]
71      }
72
73      if (defined(invoker.other_deps)) {
74        deps += invoker.other_deps
75      }
76
77      args = [
78               "--language",
79               invoker.language,
80               "--include-file",
81               _includes[0],
82               "--compile-dir",
83               rebase_path(invoker.compile_dir, root_build_dir),
84               "--out-dir",
85               rebase_path(_out_dir, root_build_dir),
86               "--sources",
87             ] + rebase_path(invoker.sources, root_build_dir)
88
89      if (defined(invoker.plugin)) {
90        inputs = [ invoker.plugin ]
91        args +=
92            [ "--plugin-path=" + rebase_path(invoker.plugin, root_build_dir) ]
93      }
94
95      if (defined(invoker.outputs)) {
96        outputs = invoker.outputs
97      } else {
98        stamp = true
99      }
100
101      if (defined(invoker.metadata)) {
102        metadata = invoker.metadata
103      }
104    }
105
106    # Output a .json file with information about this proto library.
107    _proto_info = {
108      label = get_label_info(":${invoker.target_name}", "label_no_toolchain")
109      protoc_outputs =
110          rebase_path(get_target_outputs(":$target_name._gen"), root_build_dir)
111      root = rebase_path(_out_dir, root_build_dir)
112      package = invoker.package
113
114      nested_in_python_package = ""
115      if (defined(invoker.python_package)) {
116        nested_in_python_package =
117            get_label_info(invoker.python_package, "label_no_toolchain")
118      }
119
120      dependencies = []
121      foreach(dep, invoker.deps) {
122        dependencies +=
123            rebase_path([ get_label_info(dep, "target_gen_dir") + "/" +
124                              get_label_info(dep, "name") + ".json" ],
125                        root_build_dir)
126      }
127    }
128    write_file("$target_gen_dir/$target_name.json", _proto_info, "json")
129  } else {
130    # protoc is only ever invoked from pw_protobuf_compiler_TOOLCHAIN.
131    not_needed([ "target_name" ])
132    not_needed(invoker, "*")
133  }
134}
135
136# Generates pw_protobuf C++ code for proto files, creating a source_set of the
137# generated files. This is internal and should not be used outside of this file.
138# Use pw_proto_library instead.
139template("_pw_pwpb_proto_library") {
140  _pw_invoke_protoc(target_name) {
141    forward_variables_from(invoker, "*", _forwarded_vars)
142    language = "pwpb"
143    plugin = "$dir_pw_protobuf/py/pw_protobuf/plugin.py"
144    python_deps = [ "$dir_pw_protobuf/py" ]
145  }
146
147  # Create a library with the generated source files.
148  config("$target_name._include_path") {
149    include_dirs = [ "${invoker.base_out_dir}/pwpb" ]
150    visibility = [ ":*" ]
151  }
152
153  pw_source_set(target_name) {
154    forward_variables_from(invoker, _forwarded_vars)
155    public_configs = [ ":$target_name._include_path" ]
156    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
157    public_deps = [
158                    "$dir_pw_containers:vector",
159                    dir_pw_assert,
160                    dir_pw_protobuf,
161                    dir_pw_result,
162                    dir_pw_status,
163                  ] + invoker.deps
164    sources = invoker.outputs
165    public = filter_include(sources, [ "*.pwpb.h" ])
166  }
167}
168
169# Generates nanopb RPC code for proto files, creating a source_set of the
170# generated files. This is internal and should not be used outside of this file.
171# Use pw_proto_library instead.
172template("_pw_nanopb_rpc_proto_library") {
173  # Create a target which runs protoc configured with the nanopb_rpc plugin to
174  # generate the C++ proto RPC headers.
175  _pw_invoke_protoc(target_name) {
176    forward_variables_from(invoker, "*", _forwarded_vars)
177    language = "nanopb_rpc"
178    plugin = "$dir_pw_rpc/py/pw_rpc/plugin_nanopb.py"
179    python_deps = [ "$dir_pw_rpc/py" ]
180  }
181
182  # Create a library with the generated source files.
183  config("$target_name._include_path") {
184    include_dirs = [ "${invoker.base_out_dir}/nanopb_rpc" ]
185    visibility = [ ":*" ]
186  }
187
188  pw_source_set(target_name) {
189    forward_variables_from(invoker, _forwarded_vars)
190    public_configs = [ ":$target_name._include_path" ]
191    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
192    public_deps = [
193                    ":${invoker.base_target}.nanopb",
194                    "$dir_pw_rpc:server",
195                    "$dir_pw_rpc/nanopb:client_api",
196                    "$dir_pw_rpc/nanopb:server_api",
197                    "$dir_pw_third_party/nanopb",
198                  ] + invoker.deps
199    public = invoker.outputs
200    check_includes = false
201  }
202}
203
204# Generates nanopb code for proto files, creating a source_set of the generated
205# files. This is internal and should not be used outside of this file. Use
206# pw_proto_library instead.
207template("_pw_nanopb_proto_library") {
208  # When compiling with the Nanopb plugin, the nanopb.proto file is already
209  # compiled internally, so skip recompiling it with protoc.
210  if (rebase_path(invoker.sources, invoker.compile_dir) == [ "nanopb.proto" ]) {
211    group("$target_name._gen") {
212      deps = [
213        ":${invoker.base_target}._sources($pw_protobuf_compiler_TOOLCHAIN)",
214      ]
215    }
216
217    group("$target_name") {
218      deps = invoker.deps +
219             [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
220    }
221  } else {
222    # Create a target which runs protoc configured with the nanopb plugin to
223    # generate the C proto sources.
224    _pw_invoke_protoc(target_name) {
225      forward_variables_from(invoker, "*", _forwarded_vars)
226      language = "nanopb"
227      plugin = "$dir_pw_third_party_nanopb/generator/protoc-gen-nanopb"
228      other_deps = [ "$dir_pw_third_party/nanopb:generate_nanopb_proto.action" ]
229    }
230
231    # Create a library with the generated source files.
232    config("$target_name._include_path") {
233      include_dirs = [ "${invoker.base_out_dir}/nanopb" ]
234      visibility = [ ":*" ]
235    }
236
237    pw_source_set(target_name) {
238      forward_variables_from(invoker, _forwarded_vars)
239      public_configs = [ ":$target_name._include_path" ]
240      deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
241      public_deps = [ "$dir_pw_third_party/nanopb" ] + invoker.deps
242      sources = invoker.outputs
243      public = filter_include(sources, [ "*.pb.h" ])
244    }
245  }
246}
247
248# Generates raw RPC code for proto files, creating a source_set of the generated
249# files. This is internal and should not be used outside of this file. Use
250# pw_proto_library instead.
251template("_pw_raw_rpc_proto_library") {
252  # Create a target which runs protoc configured with the nanopb_rpc plugin to
253  # generate the C++ proto RPC headers.
254  _pw_invoke_protoc(target_name) {
255    forward_variables_from(invoker, "*", _forwarded_vars)
256    language = "raw_rpc"
257    plugin = "$dir_pw_rpc/py/pw_rpc/plugin_raw.py"
258    python_deps = [ "$dir_pw_rpc/py" ]
259  }
260
261  # Create a library with the generated source files.
262  config("$target_name._include_path") {
263    include_dirs = [ "${invoker.base_out_dir}/raw_rpc" ]
264    visibility = [ ":*" ]
265  }
266
267  pw_source_set(target_name) {
268    forward_variables_from(invoker, _forwarded_vars)
269    public_configs = [ ":$target_name._include_path" ]
270    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
271    public_deps = [
272                    "$dir_pw_rpc:server",
273                    "$dir_pw_rpc/raw:client_api",
274                    "$dir_pw_rpc/raw:server_api",
275                  ] + invoker.deps
276    public = invoker.outputs
277    check_includes = false
278  }
279}
280
281# Generates Go code for proto files, listing the proto output directory in the
282# metadata variable GOPATH. Internal use only.
283template("_pw_go_proto_library") {
284  _proto_gopath = "$root_gen_dir/go"
285
286  _pw_invoke_protoc(target_name) {
287    forward_variables_from(invoker, "*")
288    language = "go"
289    metadata = {
290      gopath = [ "GOPATH+=" + rebase_path(_proto_gopath) ]
291      external_deps = [
292        "github.com/golang/protobuf/proto",
293        "google.golang.org/grpc",
294      ]
295    }
296
297    # Override the default "$base_out_dir/$language" output path.
298    out_dir = "$_proto_gopath/src"
299  }
300
301  group(target_name) {
302    deps =
303        invoker.deps + [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
304  }
305}
306
307# Generates Python code for proto files, creating a pw_python_package containing
308# the generated files. This is internal and should not be used outside of this
309# file. Use pw_proto_library instead.
310template("_pw_python_proto_library") {
311  _pw_invoke_protoc(target_name) {
312    forward_variables_from(invoker, "*", _forwarded_vars + [ "python_package" ])
313    language = "python"
314    python_deps = [ "$dir_pw_protobuf_compiler:protobuf_requirements" ]
315
316    if (defined(invoker.python_package)) {
317      python_package = invoker.python_package
318    }
319  }
320
321  if (defined(invoker.python_package) && invoker.python_package != "") {
322    # This package is nested in another Python package. Depending on this
323    # its python subtarget is equivalent to depending on the Python package it
324    # is nested in.
325    pw_python_group(target_name) {
326      python_deps = [ invoker.python_package ]
327    }
328
329    # This proto library is merged into another package, but create a target to
330    # collect its dependencies that the other package can depend on.
331    pw_python_group(target_name + "._deps") {
332      python_deps = invoker.deps
333      other_deps =
334          [ ":${invoker.target_name}._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
335    }
336  } else {
337    # Create a Python package with the generated source files.
338    pw_python_package(target_name) {
339      forward_variables_from(invoker, _forwarded_vars)
340      generate_setup = {
341        metadata = {
342          name = invoker._package_dir
343          version =
344              "0.0.1"  # TODO(hepler): Need to be able to set this verison.
345        }
346      }
347      sources = invoker.outputs
348      strip_prefix = "${invoker.base_out_dir}/python"
349      python_deps = invoker.deps
350      other_deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
351      static_analysis = []
352
353      _pw_module_as_package = invoker.module_as_package != ""
354    }
355  }
356}
357
358# Generates protobuf code from .proto definitions for various languages.
359# For each supported generator, creates a sub-target named:
360#
361#   <target_name>.<generator>
362#
363# GN permits using abbreviated labels when the target name matches the directory
364# name (e.g. //foo for //foo:foo). For consistency with this, the sub-targets
365# for each generator are aliased to the directory when the target name is the
366# same. For example, these two labels are equivalent:
367#
368#   //path/to/my_protos:my_protos.pwpb
369#   //path/to/my_protos:pwpb
370#
371# pw_protobuf_library targets generate Python packages. As such, they must have
372# globally unique package names. The first directory of the prefix or the first
373# common directory of the sources is used as the Python package.
374#
375# Args:
376#   sources: List of input .proto files.
377#   deps: List of other pw_proto_library dependencies.
378#   inputs: Other files on which the protos depend (e.g. nanopb .options files).
379#   prefix: A prefix to add to the source protos prior to compilation. For
380#       example, a source called "foo.proto" with prefix = "nested" will be
381#       compiled with protoc as "nested/foo.proto".
382#   strip_prefix: Remove this prefix from the source protos. All source and
383#       input files must be nested under this path.
384#   python_package: Label of Python package to which to add the proto modules.
385#       The .python subtarget will redirect to this package.
386#
387template("pw_proto_library") {
388  assert(defined(invoker.sources) && invoker.sources != [],
389         "pw_proto_library requires .proto source files")
390
391  if (defined(invoker.python_module_as_package)) {
392    _module_as_package = invoker.python_module_as_package
393
394    _must_be_one_source = invoker.sources
395    assert([ _must_be_one_source[0] ] == _must_be_one_source,
396           "'python_module_as_package' requires exactly one source file")
397    assert(_module_as_package != "",
398           "'python_module_as_package' cannot be be empty")
399    assert(string_split(_module_as_package, "/") == [ _module_as_package ],
400           "'python_module_as_package' cannot contain slashes")
401    assert(!defined(invoker.prefix),
402           "'prefix' cannot be provided with 'python_module_as_package'")
403  } else {
404    _module_as_package = ""
405  }
406
407  if (defined(invoker.strip_prefix)) {
408    _source_root = get_path_info(invoker.strip_prefix, "abspath")
409  } else {
410    _source_root = get_path_info(".", "abspath")
411  }
412
413  if (defined(invoker.prefix)) {
414    _prefix = invoker.prefix
415  } else {
416    _prefix = ""
417  }
418
419  _package_dir = ""
420  _source_names = []
421
422  # Determine the Python package name to use for these protos. If there is no
423  # prefix, the first directory the sources are nested under is used.
424  foreach(source, rebase_path(invoker.sources, _source_root)) {
425    _path_components = []
426    _path_components = string_split(source, "/")
427
428    if (_package_dir == "") {
429      _package_dir = _path_components[0]
430    } else {
431      assert(_prefix != "" || _path_components[0] == _package_dir,
432             "Unless 'prefix' is supplied, all .proto sources in a " +
433                 "pw_proto_library must be in the same directory tree")
434    }
435
436    _source_names +=
437        [ get_path_info(source, "dir") + "/" + get_path_info(source, "name") ]
438  }
439
440  # If the 'prefix' was supplied, use that for the package directory.
441  if (_prefix != "") {
442    _prefix_path_components = string_split(_prefix, "/")
443    _package_dir = _prefix_path_components[0]
444  }
445
446  assert(_package_dir != "" && _package_dir != "." && _package_dir != "..",
447         "Either a 'prefix' must be specified or all sources must be nested " +
448             "under a common directory")
449
450  # Define an action that is never executed to prevent duplicate proto packages
451  # from being declared. The target name and the output file include only the
452  # package directory, so different targets that use the same proto package name
453  # will conflict.
454  action("pw_proto_library.$_package_dir") {
455    script = "$dir_pw_build/py/pw_build/nop.py"
456    visibility = []
457
458    # Place an error message in the output path (which is never created). If the
459    # package name conflicts occur in different BUILD.gn files, this results in
460    # an otherwise cryptic Ninja error, rather than a GN error.
461    outputs = [ "$root_out_dir/ " +
462                "ERROR - Multiple pw_proto_library targets create the " +
463                "'$_package_dir' package. Change the package name by setting " +
464                "the \"prefix\" arg or move the protos to a different " +
465                "directory, then re-run gn gen." ]
466  }
467
468  if (defined(invoker.deps)) {
469    _deps = invoker.deps
470  } else {
471    _deps = []
472  }
473
474  _common = {
475    base_target = target_name
476
477    # This is the output directory for all files related to this proto library.
478    # Sources are mirrored to "$base_out_dir/sources" and protoc puts outputs in
479    # "$base_out_dir/$language" by default.
480    base_out_dir =
481        get_label_info(":$target_name($pw_protobuf_compiler_TOOLCHAIN)",
482                       "target_gen_dir") + "/$target_name.proto_library"
483
484    compile_dir = "$base_out_dir/sources"
485
486    # Refer to the source files as the are mirrored to the output directory.
487    sources = []
488    foreach(file, rebase_path(invoker.sources, _source_root)) {
489      sources += [ "$compile_dir/$_prefix/$file" ]
490    }
491
492    package = _package_dir
493  }
494
495  # For each proto target, create a file which collects the base directories of
496  # all of its dependencies to list as include paths to protoc.
497  generated_file("$target_name._includes") {
498    # Collect metadata from the include path files of each dependency.
499
500    deps = []
501    foreach(dep, _deps) {
502      _base = get_label_info(dep, "label_no_toolchain")
503      deps += [ "$_base._includes(" + get_label_info(dep, "toolchain") + ")" ]
504    }
505
506    data_keys = [ "protoc_includes" ]
507    outputs = [ "${_common.base_out_dir}/includes.txt" ]
508
509    # Indicate this library's base directory for its dependents.
510    metadata = {
511      protoc_includes = [ rebase_path(_common.compile_dir, root_build_dir) ]
512    }
513  }
514
515  # Mirror the proto sources to the output directory with the prefix added.
516  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
517    pw_mirror_tree("$target_name._sources") {
518      source_root = _source_root
519      sources = invoker.sources
520
521      if (defined(invoker.inputs)) {
522        sources += invoker.inputs
523      }
524
525      directory = "${_common.compile_dir}/$_prefix"
526    }
527  } else {
528    not_needed(invoker, [ "inputs" ])
529  }
530
531  # Enumerate all of the protobuf generator targets.
532
533  _pw_pwpb_proto_library("$target_name.pwpb") {
534    forward_variables_from(invoker, _forwarded_vars)
535    forward_variables_from(_common, "*")
536
537    deps = []
538    foreach(dep, _deps) {
539      _base = get_label_info(dep, "label_no_toolchain")
540      deps += [ "$_base.pwpb(" + get_label_info(dep, "toolchain") + ")" ]
541    }
542
543    outputs = []
544    foreach(name, _source_names) {
545      outputs += [ "$base_out_dir/pwpb/$_prefix/${name}.pwpb.h" ]
546    }
547  }
548
549  if (dir_pw_third_party_nanopb != "") {
550    _pw_nanopb_rpc_proto_library("$target_name.nanopb_rpc") {
551      forward_variables_from(invoker, _forwarded_vars)
552      forward_variables_from(_common, "*")
553
554      deps = []
555      foreach(dep, _deps) {
556        _lbl = get_label_info(dep, "label_no_toolchain")
557        deps += [ "$_lbl.nanopb_rpc(" + get_label_info(dep, "toolchain") + ")" ]
558      }
559
560      outputs = []
561      foreach(name, _source_names) {
562        outputs += [ "$base_out_dir/nanopb_rpc/$_prefix/${name}.rpc.pb.h" ]
563      }
564    }
565
566    _pw_nanopb_proto_library("$target_name.nanopb") {
567      forward_variables_from(invoker, _forwarded_vars)
568      forward_variables_from(_common, "*")
569
570      deps = []
571      foreach(dep, _deps) {
572        _base = get_label_info(dep, "label_no_toolchain")
573        deps += [ "$_base.nanopb(" + get_label_info(dep, "toolchain") + ")" ]
574      }
575
576      outputs = []
577      foreach(name, _source_names) {
578        outputs += [
579          "$base_out_dir/nanopb/$_prefix/${name}.pb.h",
580          "$base_out_dir/nanopb/$_prefix/${name}.pb.c",
581        ]
582      }
583    }
584  } else {
585    pw_error("$target_name.nanopb_rpc") {
586      message =
587          "\$dir_pw_third_party_nanopb must be set to generate nanopb RPC code."
588    }
589
590    pw_error("$target_name.nanopb") {
591      message =
592          "\$dir_pw_third_party_nanopb must be set to compile nanopb protobufs."
593    }
594  }
595
596  _pw_raw_rpc_proto_library("$target_name.raw_rpc") {
597    forward_variables_from(invoker, _forwarded_vars)
598    forward_variables_from(_common, "*")
599
600    deps = []
601    foreach(dep, _deps) {
602      _base = get_label_info(dep, "label_no_toolchain")
603      deps += [ "$_base.raw_rpc(" + get_label_info(dep, "toolchain") + ")" ]
604    }
605
606    outputs = []
607    foreach(name, _source_names) {
608      outputs += [ "$base_out_dir/raw_rpc/$_prefix/${name}.raw_rpc.pb.h" ]
609    }
610  }
611
612  _pw_go_proto_library("$target_name.go") {
613    sources = _common.sources
614
615    deps = []
616    foreach(dep, _deps) {
617      _base = get_label_info(dep, "label_no_toolchain")
618      deps += [ "$_base.go(" + get_label_info(dep, "toolchain") + ")" ]
619    }
620
621    forward_variables_from(_common, "*")
622  }
623
624  _pw_python_proto_library("$target_name.python") {
625    forward_variables_from(_common, "*")
626    forward_variables_from(invoker, [ "python_package" ])
627    module_as_package = _module_as_package
628
629    deps = []
630    foreach(dep, _deps) {
631      _base = get_label_info(dep, "label_no_toolchain")
632      deps += [ "$_base.python(" + get_label_info(dep, "toolchain") + ")" ]
633    }
634
635    if (module_as_package == "") {
636      _python_prefix = "$base_out_dir/python/$_prefix"
637    } else {
638      _python_prefix = "$base_out_dir/python/$module_as_package"
639    }
640
641    outputs = []
642    foreach(name, _source_names) {
643      outputs += [
644        "$_python_prefix/${name}_pb2.py",
645        "$_python_prefix/${name}_pb2.pyi",
646      ]
647    }
648  }
649
650  # All supported pw_protobuf generators.
651  _protobuf_generators = [
652    "pwpb",
653    "nanopb",
654    "nanopb_rpc",
655    "raw_rpc",
656    "go",
657    "python",
658  ]
659
660  # If the label matches the directory name, alias the subtargets to the
661  # directory (e.g. //foo:nanopb is an alias for //foo:foo.nanopb).
662  if (get_label_info(":$target_name", "name") ==
663      get_path_info(get_label_info(":$target_name", "dir"), "name")) {
664    foreach(_generator, _protobuf_generators - [ "python" ]) {
665      group(_generator) {
666        public_deps = [ ":${invoker.target_name}.$_generator" ]
667      }
668    }
669
670    pw_python_group("python") {
671      python_deps = [ ":${invoker.target_name}.python" ]
672    }
673  }
674
675  # If the user attempts to use the target directly instead of one of the
676  # generator targets, run a script which prints a nice error message.
677  pw_python_action(target_name) {
678    script = string_join("/",
679                         [
680                           dir_pw_protobuf_compiler,
681                           "py",
682                           "pw_protobuf_compiler",
683                           "proto_target_invalid.py",
684                         ])
685    args = [
686             "--target",
687             target_name,
688             "--dir",
689             get_path_info(".", "abspath"),
690             "--root",
691             "//",
692           ] + _protobuf_generators
693    stamp = true
694  }
695}
696