1# Copyright 2022 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"""WORK IN PROGRESS! 15 16# Overview of implementation 17 18(If you just want to use the macros, see their docstrings; this section is 19intended to orient future maintainers.) 20 21Proto code generation is carried out by the _pwpb_proto_library, 22_nanopb_proto_library, _pw_raw_rpc_proto_library and 23_pw_nanopb_rpc_proto_library rules using aspects 24(https://docs.bazel.build/versions/main/skylark/aspects.html). 25 26As an example, _pwpb_proto_library has a single proto_library as a dependency, 27but that proto_library may depend on other proto_library targets; as a result, 28the generated .pwpb.h file #include's .pwpb.h files generated from the 29dependency proto_libraries. The aspect propagates along the proto_library 30dependency graph, running the proto compiler on each proto_library in the 31original target's transitive dependencies, ensuring that we're not missing any 32.pwpb.h files at C++ compile time. 33 34Although we have a separate rule for each protocol compiler plugin 35(_pwpb_proto_library, _nanopb_proto_library, _pw_raw_rpc_proto_library, 36_pw_nanopb_rpc_proto_library), they actually share an implementation 37(_impl_pw_proto_library) and use similar aspects, all generated by 38_proto_compiler_aspect. 39""" 40 41load("@bazel_skylib//lib:paths.bzl", "paths") 42load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "use_cpp_toolchain") 43load( 44 "@pigweed//pw_build/bazel_internal:pigweed_internal.bzl", 45 _compile_cc = "compile_cc", 46) 47load("@rules_proto//proto:defs.bzl", "ProtoInfo") 48 49# For Copybara use only 50ADDITIONAL_PWPB_DEPS = [] 51 52def pwpb_proto_library(name, deps, tags = None, visibility = None): 53 """A C++ proto library generated using pw_protobuf. 54 55 Attributes: 56 deps: proto_library targets for which to generate this library. 57 """ 58 _pwpb_proto_library( 59 name = name, 60 protos = deps, 61 deps = [ 62 Label("//pw_assert"), 63 Label("//pw_containers:vector"), 64 Label("//pw_preprocessor"), 65 Label("//pw_protobuf"), 66 Label("//pw_result"), 67 Label("//pw_span"), 68 Label("//pw_status"), 69 Label("//pw_string:string"), 70 ] + ADDITIONAL_PWPB_DEPS, 71 tags = tags, 72 visibility = visibility, 73 ) 74 75def pwpb_rpc_proto_library(name, deps, pwpb_proto_library_deps, tags = None, visibility = None): 76 """A pwpb_rpc proto library target. 77 78 Attributes: 79 deps: proto_library targets for which to generate this library. 80 pwpb_proto_library_deps: A pwpb_proto_library generated 81 from the same proto_library. Required. 82 """ 83 _pw_pwpb_rpc_proto_library( 84 name = name, 85 protos = deps, 86 deps = [ 87 Label("//pw_protobuf"), 88 Label("//pw_rpc"), 89 Label("//pw_rpc/pwpb:client_api"), 90 Label("//pw_rpc/pwpb:server_api"), 91 ] + pwpb_proto_library_deps, 92 tags = tags, 93 visibility = visibility, 94 ) 95 96def raw_rpc_proto_library(name, deps, tags = None, visibility = None): 97 """A raw C++ RPC proto library.""" 98 _pw_raw_rpc_proto_library( 99 name = name, 100 protos = deps, 101 deps = [ 102 Label("//pw_rpc"), 103 Label("//pw_rpc/raw:client_api"), 104 Label("//pw_rpc/raw:server_api"), 105 ], 106 tags = tags, 107 visibility = visibility, 108 ) 109 110# TODO: b/234873954 - Enable unused variable check. 111# buildifier: disable=unused-variable 112def nanopb_proto_library(name, deps, tags = [], visibility = None, options = None): 113 """A C++ proto library generated using pw_protobuf. 114 115 Attributes: 116 deps: proto_library targets for which to generate this library. 117 """ 118 119 # TODO(tpudlik): Find a way to get Nanopb to generate nested structs. 120 # Otherwise add the manual tag to the resulting library, preventing it 121 # from being built unless directly depended on. e.g. The 'Pigweed' 122 # message in 123 # pw_protobuf/pw_protobuf_test_protos/full_test.proto will fail to 124 # compile as it has a self referring nested message. According to 125 # the docs 126 # https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options 127 # and https://github.com/nanopb/nanopb/issues/433 it seems like it 128 # should be possible to configure nanopb to generate nested structs via 129 # flags in .options files. 130 # 131 # One issue is nanopb doesn't silently ignore unknown options in .options 132 # files so we can't share .options files with pwpb. 133 extra_tags = ["manual"] 134 _nanopb_proto_library( 135 name = name, 136 protos = deps, 137 deps = [ 138 "@com_github_nanopb_nanopb//:nanopb", 139 Label("//pw_assert"), 140 Label("//pw_containers:vector"), 141 Label("//pw_preprocessor"), 142 Label("//pw_result"), 143 Label("//pw_span"), 144 Label("//pw_status"), 145 Label("//pw_string:string"), 146 ], 147 tags = tags + extra_tags, 148 visibility = visibility, 149 ) 150 151def nanopb_rpc_proto_library(name, deps, nanopb_proto_library_deps, tags = [], visibility = None): 152 """A C++ RPC proto library using nanopb. 153 154 Attributes: 155 deps: proto_library targets for which to generate this library. 156 nanopb_proto_library_deps: A pw_nanopb_cc_library generated 157 from the same proto_library. Required. 158 """ 159 160 # See comment in nanopb_proto_library. 161 extra_tags = ["manual"] 162 _pw_nanopb_rpc_proto_library( 163 name = name, 164 protos = deps, 165 # TODO: b/339280821 - This is required to avoid breaking internal 166 # Google builds but shouldn't matter for any external user. Remove this 167 # when possible. 168 features = ["-layering_check"], 169 deps = [ 170 Label("//pw_rpc"), 171 Label("//pw_rpc/nanopb:client_api"), 172 Label("//pw_rpc/nanopb:server_api"), 173 ] + nanopb_proto_library_deps, 174 tags = tags + extra_tags, 175 visibility = visibility, 176 ) 177 178def pw_proto_library( 179 name, 180 deps, 181 visibility = None, 182 tags = [], 183 nanopb_options = None, 184 enabled_targets = None): 185 """Generate Pigweed proto C++ code. 186 187 DEPRECATED. This macro is deprecated and will be removed in a future 188 Pigweed version. Please use the single-target macros above. 189 190 Args: 191 name: The name of the target. 192 deps: proto_library targets from which to generate Pigweed C++. 193 visibility: The visibility of the target. See 194 https://bazel.build/concepts/visibility. 195 tags: Tags for the target. See 196 https://bazel.build/reference/be/common-definitions#common-attributes. 197 nanopb_options: path to file containing nanopb options, if any 198 (https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options). 199 enabled_targets: Specifies which libraries should be generated. Libraries 200 will only be generated as needed, but unnecessary outputs may conflict 201 with other build rules and thus cause build failures. This filter allows 202 manual selection of which libraries should be supported by this build 203 target in order to prevent such conflicts. The argument, if provided, 204 should be a subset of ["pwpb", "nanopb", "raw_rpc", "nanopb_rpc"]. All 205 are enabled by default. Note that "nanopb_rpc" relies on "nanopb". 206 207 Example usage: 208 209 proto_library( 210 name = "benchmark_proto", 211 srcs = [ 212 "benchmark.proto", 213 ], 214 ) 215 216 pw_proto_library( 217 name = "benchmark_pw_proto", 218 deps = [":benchmark_proto"], 219 ) 220 221 pw_cc_binary( 222 name = "proto_user", 223 srcs = ["proto_user.cc"], 224 deps = [":benchmark_pw_proto.pwpb"], 225 ) 226 227 The pw_proto_library generates the following targets in this example: 228 229 "benchmark_pw_proto.pwpb": C++ library exposing the "benchmark.pwpb.h" header. 230 "benchmark_pw_proto.pwpb_rpc": C++ library exposing the 231 "benchmark.rpc.pwpb.h" header. 232 "benchmark_pw_proto.raw_rpc": C++ library exposing the "benchmark.raw_rpc.h" 233 header. 234 "benchmark_pw_proto.nanopb": C++ library exposing the "benchmark.pb.h" 235 header. 236 "benchmark_pw_proto.nanopb_rpc": C++ library exposing the 237 "benchmark.rpc.pb.h" header. 238 """ 239 240 def is_plugin_enabled(plugin): 241 return (enabled_targets == None or plugin in enabled_targets) 242 243 if is_plugin_enabled("nanopb"): 244 # Use nanopb to generate the pb.h and pb.c files, and the target 245 # exposing them. 246 nanopb_proto_library( 247 name = name + ".nanopb", 248 deps = deps, 249 tags = tags, 250 visibility = visibility, 251 options = nanopb_options, 252 ) 253 254 if is_plugin_enabled("pwpb"): 255 pwpb_proto_library( 256 name = name + ".pwpb", 257 deps = deps, 258 tags = tags, 259 visibility = visibility, 260 ) 261 262 if is_plugin_enabled("pwpb_rpc"): 263 pwpb_rpc_proto_library( 264 name = name + ".pwpb_rpc", 265 deps = deps, 266 pwpb_proto_library_deps = [":" + name + ".pwpb"], 267 tags = tags, 268 visibility = visibility, 269 ) 270 271 if is_plugin_enabled("raw_rpc"): 272 raw_rpc_proto_library( 273 name = name + ".raw_rpc", 274 deps = deps, 275 tags = tags, 276 visibility = visibility, 277 ) 278 279 if is_plugin_enabled("nanopb_rpc"): 280 nanopb_rpc_proto_library( 281 name = name + ".nanopb_rpc", 282 deps = deps, 283 nanopb_proto_library_deps = [":" + name + ".nanopb"], 284 tags = tags, 285 visibility = visibility, 286 ) 287 288PwProtoInfo = provider( 289 "Returned by PW proto compilation aspect", 290 fields = { 291 "hdrs": "generated C++ header files", 292 "includes": "include paths for generated C++ header files", 293 "srcs": "generated C++ src files", 294 }, 295) 296 297PwProtoOptionsInfo = provider( 298 "Allows `pw_proto_filegroup` targets to pass along `.options` files " + 299 "without polluting the `DefaultInfo` provider, which means they can " + 300 "still be used in the `srcs` of `proto_library` targets.", 301 fields = { 302 "options_files": (".options file(s) associated with a proto_library " + 303 "for Pigweed codegen."), 304 }, 305) 306 307def _proto_compiler_aspect_impl(target, ctx): 308 # List the files we will generate for this proto_library target. 309 proto_info = target[ProtoInfo] 310 311 srcs = [] 312 hdrs = [] 313 314 # Setup the output root for the plugin to point to targets output 315 # directory. This allows us to declare the location of the files that protoc 316 # will output in a way that `ctx.actions.declare_file` will understand, 317 # since it works relative to the target. 318 out_path = ctx.bin_dir.path 319 if target.label.workspace_root: 320 out_path += "/" + target.label.workspace_root 321 if target.label.package: 322 out_path += "/" + target.label.package 323 324 # Add location of headers to cc include path. 325 # Depending on prefix rules, the include path can be directly from the 326 # output path, or underneath the package. 327 includes = [out_path] 328 329 for src in proto_info.direct_sources: 330 # Get the relative import path for this .proto file. 331 src_rel = paths.relativize(src.path, proto_info.proto_source_root) 332 proto_dir = paths.dirname(src_rel) 333 334 # Add location of headers to cc include path. 335 includes.append("{}/{}".format(out_path, src.owner.package)) 336 337 for ext in ctx.attr._extensions: 338 # Declare all output files, in target package dir. 339 generated_filename = src.basename[:-len("proto")] + ext 340 if proto_dir: 341 out_file_name = "{}/{}".format( 342 proto_dir, 343 generated_filename, 344 ) 345 else: 346 out_file_name = generated_filename 347 348 out_file = ctx.actions.declare_file(out_file_name) 349 350 if ext.endswith(".h"): 351 hdrs.append(out_file) 352 else: 353 srcs.append(out_file) 354 355 # List the `.options` files from any `pw_proto_filegroup` targets listed 356 # under this target's `srcs`. 357 options_files = [ 358 options_file 359 for src in ctx.rule.attr.srcs 360 if PwProtoOptionsInfo in src 361 for options_file in src[PwProtoOptionsInfo].options_files.to_list() 362 ] 363 364 # Local repository options files. 365 options_file_include_paths = [paths.join(".", ctx.rule.attr.strip_import_prefix.lstrip("/"))] 366 for options_file in options_files: 367 # Handle .options files residing in external repositories. 368 if options_file.owner.workspace_root: 369 options_file_include_paths.append( 370 paths.join( 371 options_file.owner.workspace_root, 372 ctx.rule.attr.strip_import_prefix.lstrip("/"), 373 ), 374 ) 375 376 # Handle generated .options files. 377 if options_file.root.path: 378 options_file_include_paths.append( 379 paths.join( 380 options_file.root.path, 381 ctx.rule.attr.strip_import_prefix.lstrip("/"), 382 ), 383 ) 384 385 args = ctx.actions.args() 386 for path in proto_info.transitive_proto_path.to_list(): 387 args.add("-I{}".format(path)) 388 389 args.add("--plugin=protoc-gen-custom={}".format(ctx.executable._protoc_plugin.path)) 390 391 # Convert include paths to a depset and back to deduplicate entries. 392 for options_file_include_path in depset(options_file_include_paths).to_list(): 393 args.add("--custom_opt=-I{}".format(options_file_include_path)) 394 395 for plugin_option in ctx.attr._plugin_options: 396 # if import_prefix is set, the .proto is placed under a virtual include path 397 # prefixed by `import_prefix`. That path is what is given to the proto 398 # plugin via plugin_pb2.CodeGeneratorRequest.proto_file.name, so the include 399 # paths we give to the plugin need to be able find the .options files based 400 # on the following logic in pw_protobuf/options.py: 401 # 402 # options_file_name = include_path / proto_file_name.with_suffix(".options") 403 # 404 # This means that in order for the plugin to find the .options file, we need 405 # to let the plugin know the import prefix so it can modify the `proto_file_name` 406 # back to the original to be able to find the .options file. 407 if plugin_option == "--import-prefix={}": 408 if ctx.rule.attr.import_prefix: 409 plugin_option = plugin_option.format(ctx.rule.attr.import_prefix) 410 else: 411 continue 412 args.add("--custom_opt={}".format(plugin_option)) 413 414 args.add("--custom_out={}".format(out_path)) 415 args.add_all(proto_info.direct_sources) 416 417 all_tools = [ 418 ctx.executable._protoc, 419 ctx.executable._python_runtime, 420 ctx.executable._protoc_plugin, 421 ] 422 run_path = [tool.dirname for tool in all_tools] 423 424 ctx.actions.run( 425 inputs = depset( 426 direct = proto_info.direct_sources + 427 proto_info.transitive_sources.to_list() + 428 options_files, 429 transitive = [proto_info.transitive_descriptor_sets], 430 ), 431 progress_message = "Generating %s C++ files for %s" % (ctx.attr._extensions, ctx.label.name), 432 tools = all_tools, 433 outputs = srcs + hdrs, 434 executable = ctx.executable._protoc, 435 arguments = [args], 436 env = { 437 438 # The nanopb protobuf plugin likes to compile some temporary protos 439 # next to source files. This forces them to be written to Bazel's 440 # genfiles directory. 441 "NANOPB_PB2_TEMP_DIR": str(ctx.genfiles_dir), 442 "PATH": ":".join(run_path), 443 }, 444 ) 445 446 transitive_srcs = srcs 447 transitive_hdrs = hdrs 448 transitive_includes = includes 449 for dep in ctx.rule.attr.deps: 450 transitive_srcs += dep[PwProtoInfo].srcs 451 transitive_hdrs += dep[PwProtoInfo].hdrs 452 transitive_includes += dep[PwProtoInfo].includes 453 return [PwProtoInfo( 454 srcs = transitive_srcs, 455 hdrs = transitive_hdrs, 456 includes = transitive_includes, 457 )] 458 459def _proto_compiler_aspect(extensions, protoc_plugin, plugin_options = []): 460 """Returns an aspect that runs the proto compiler. 461 462 The aspect propagates through the deps of proto_library targets, running 463 the proto compiler with the specified plugin for each of their source 464 files. The proto compiler is assumed to produce one output file per input 465 .proto file. That file is placed under bazel-bin at the same path as the 466 input file, but with the specified extension (i.e., with _extensions = [ 467 .pwpb.h], the aspect converts pw_log/log.proto into 468 bazel-bin/pw_log/log.pwpb.h). 469 470 The aspect returns a provider exposing all the File objects generated from 471 the dependency graph. 472 """ 473 return aspect( 474 attr_aspects = ["deps"], 475 attrs = { 476 "_extensions": attr.string_list(default = extensions), 477 "_plugin_options": attr.string_list( 478 default = plugin_options, 479 ), 480 "_protoc": attr.label( 481 default = Label("@com_google_protobuf//:protoc"), 482 executable = True, 483 cfg = "exec", 484 ), 485 "_protoc_plugin": attr.label( 486 default = Label(protoc_plugin), 487 executable = True, 488 cfg = "exec", 489 ), 490 "_python_runtime": attr.label( 491 default = Label("//:python3_interpreter"), 492 allow_single_file = True, 493 executable = True, 494 cfg = "exec", 495 ), 496 }, 497 implementation = _proto_compiler_aspect_impl, 498 provides = [PwProtoInfo], 499 ) 500 501def _impl_pw_proto_library(ctx): 502 """Implementation of the proto codegen rule. 503 504 The work of actually generating the code is done by the aspect, so here we 505 compile and return a CcInfo to link against. 506 """ 507 508 # Note that we don't distinguish between the files generated from the 509 # target, and the files generated from its dependencies. We return all of 510 # them together, and in pw_proto_library expose all of them as hdrs. 511 # Pigweed's plugins happen to only generate .h files, so this works, but 512 # strictly speaking we should expose only the files generated from the 513 # target itself in hdrs, and place the headers generated from dependencies 514 # in srcs. We don't perform layering_check in Pigweed, so this is not a big 515 # deal. 516 # 517 # TODO: b/234873954 - Tidy this up. 518 all_srcs = [] 519 all_hdrs = [] 520 all_includes = [] 521 for dep in ctx.attr.protos: 522 for f in dep[PwProtoInfo].hdrs: 523 all_hdrs.append(f) 524 for f in dep[PwProtoInfo].srcs: 525 all_srcs.append(f) 526 for i in dep[PwProtoInfo].includes: 527 all_includes.append(i) 528 529 return _compile_cc( 530 ctx, 531 all_srcs, 532 all_hdrs, 533 ctx.attr.deps, 534 all_includes, 535 defines = [], 536 ) 537 538# Instantiate the aspects and rules for generating code using specific plugins. 539_pwpb_proto_compiler_aspect = _proto_compiler_aspect( 540 ["pwpb.h"], 541 "//pw_protobuf/py:plugin", 542 ["--no-legacy-namespace", "--import-prefix={}"], 543) 544 545_pwpb_proto_library = rule( 546 implementation = _impl_pw_proto_library, 547 attrs = { 548 "deps": attr.label_list( 549 providers = [CcInfo], 550 ), 551 "protos": attr.label_list( 552 providers = [ProtoInfo], 553 aspects = [_pwpb_proto_compiler_aspect], 554 ), 555 }, 556 fragments = ["cpp"], 557 toolchains = use_cpp_toolchain(), 558) 559 560_nanopb_proto_compiler_aspect = _proto_compiler_aspect( 561 ["pb.h", "pb.c"], 562 "@com_github_nanopb_nanopb//:protoc-gen-nanopb", 563 ["--library-include-format=quote"], 564) 565 566_nanopb_proto_library = rule( 567 implementation = _impl_pw_proto_library, 568 attrs = { 569 "deps": attr.label_list( 570 providers = [CcInfo], 571 ), 572 "protos": attr.label_list( 573 providers = [ProtoInfo], 574 aspects = [_nanopb_proto_compiler_aspect], 575 ), 576 }, 577 fragments = ["cpp"], 578 toolchains = use_cpp_toolchain(), 579) 580 581_pw_pwpb_rpc_proto_compiler_aspect = _proto_compiler_aspect( 582 ["rpc.pwpb.h"], 583 "//pw_rpc/py:plugin_pwpb", 584 ["--no-legacy-namespace"], 585) 586 587_pw_pwpb_rpc_proto_library = rule( 588 implementation = _impl_pw_proto_library, 589 attrs = { 590 "deps": attr.label_list( 591 providers = [CcInfo], 592 ), 593 "protos": attr.label_list( 594 providers = [ProtoInfo], 595 aspects = [_pw_pwpb_rpc_proto_compiler_aspect], 596 ), 597 }, 598 fragments = ["cpp"], 599 toolchains = use_cpp_toolchain(), 600) 601 602_pw_raw_rpc_proto_compiler_aspect = _proto_compiler_aspect( 603 ["raw_rpc.pb.h"], 604 "//pw_rpc/py:plugin_raw", 605 ["--no-legacy-namespace"], 606) 607 608_pw_raw_rpc_proto_library = rule( 609 implementation = _impl_pw_proto_library, 610 attrs = { 611 "deps": attr.label_list( 612 providers = [CcInfo], 613 ), 614 "protos": attr.label_list( 615 providers = [ProtoInfo], 616 aspects = [_pw_raw_rpc_proto_compiler_aspect], 617 ), 618 }, 619 fragments = ["cpp"], 620 toolchains = use_cpp_toolchain(), 621) 622 623_pw_nanopb_rpc_proto_compiler_aspect = _proto_compiler_aspect( 624 ["rpc.pb.h"], 625 "//pw_rpc/py:plugin_nanopb", 626 ["--no-legacy-namespace"], 627) 628 629_pw_nanopb_rpc_proto_library = rule( 630 implementation = _impl_pw_proto_library, 631 attrs = { 632 "deps": attr.label_list( 633 providers = [CcInfo], 634 ), 635 "protos": attr.label_list( 636 providers = [ProtoInfo], 637 aspects = [_pw_nanopb_rpc_proto_compiler_aspect], 638 ), 639 }, 640 fragments = ["cpp"], 641 toolchains = use_cpp_toolchain(), 642) 643 644def _pw_proto_filegroup_impl(ctx): 645 source_files = list() 646 options_files = list() 647 648 for src in ctx.attr.srcs: 649 source_files += src.files.to_list() 650 651 for options_src in ctx.attr.options_files: 652 for file in options_src.files.to_list(): 653 if file.extension == "options": 654 options_files.append(file) 655 else: 656 fail(( 657 "Files provided as `options_files` to a " + 658 "`pw_proto_filegroup` must have the `.options` " + 659 "extension; the file `{}` was provided." 660 ).format(file.basename)) 661 662 return [ 663 DefaultInfo(files = depset(source_files)), 664 PwProtoOptionsInfo(options_files = depset(options_files)), 665 ] 666 667pw_proto_filegroup = rule( 668 doc = ( 669 "Acts like a `filegroup`, but with an additional `options_files` " + 670 "attribute that accepts a list of `.options` files. These `.options` " + 671 "files should typically correspond to `.proto` files provided under " + 672 "the `srcs` attribute." + 673 "\n\n" + 674 "A `pw_proto_filegroup` is intended to be passed into the `srcs` of " + 675 "a `proto_library` target as if it were a normal `filegroup` " + 676 "containing only `.proto` files. For the purposes of the " + 677 "`proto_library` itself, the `pw_proto_filegroup` does indeed act " + 678 "just like a normal `filegroup`; the `options_files` attribute is " + 679 "ignored. However, if that `proto_library` target is then passed " + 680 "(directly or transitively) into the `deps` of a `pw_proto_library` " + 681 "for code generation, the `pw_proto_library` target will have access " + 682 "to the provided `.options` files and will pass them to the code " + 683 "generator." + 684 "\n\n" + 685 "Note that, in order for a `pw_proto_filegroup` to be a valid `srcs` " + 686 "entry for a `proto_library`, it must meet the same conditions " + 687 "required of a standard `filegroup` in that context. Namely, its " + 688 "`srcs` must provide at least one `.proto` (or `.protodevel`) file. " + 689 "Put simply, a `pw_proto_filegroup` cannot be used as a vector for " + 690 "injecting solely `.options` files; it must contain at least one " + 691 "proto as well (generally one associated with an included `.options` " + 692 "file in the interest of clarity)." + 693 "\n\n" + 694 "Regarding the somewhat unusual usage, this feature's design was " + 695 "mostly preordained by the combination of Bazel's strict access " + 696 "controls, the restrictions imposed on inputs to the `proto_library` " + 697 "rule, and the need to support `.options` files from transitive " + 698 "dependencies." 699 ), 700 implementation = _pw_proto_filegroup_impl, 701 attrs = { 702 "options_files": attr.label_list( 703 allow_files = True, 704 ), 705 "srcs": attr.label_list( 706 allow_files = True, 707 ), 708 }, 709 provides = [PwProtoOptionsInfo], 710) 711