-
Notifications
You must be signed in to change notification settings - Fork 15.9k
Description
What language does this apply to?
It is language agnostic, and applies to the ProtoInfo
provider.
Describe the problem you are trying to solve.
When writing aspects or rules, you often need to merge a collection of ProtoInfo
providers. It turns out that right now this is not particularly easy, for the following reasons:
- This line restricts one's ability to create an empty dummy
ProtoInfo
containing only transitive deps; it will silently skip the entire set, causing no language bindings to be built. - The architecture of protoc appears to require all
.protos
to share a common base directory (theworkspace_root
). If aspects run over a collection of interfaces declared across different Bazel modules, they all have different{target, ctx}.label.workspace_root
values and so they are quietly omitted. - The constructor / intializer function for the
ProtoInfo
provider masks the underlyingtransitive_*
fields that would allow you to manually assemble aProtoInfo
containing only transitive dependencies.
Using CcInfo
and cc_common
from rules_cc
as inspiration, I think what is needed is a proto_common.merge_proto_infos(...)
function for cleanly merging ProtoInfo
providers.
Describe the solution you'd like
I have found a workaround, but it is neither elegant nor performant, and is quite specific to my application (an aspect for transforming a dependency tree of ROS interfaces to ProtoInfo
providers). The basic idea is whenever you need to merge ProtoInfo
providers from a collection of deps, you flatten the depset and symlink protos to the context's workspace_root, and finally rebundle them into a new ProtoInfo
. You can also optionally include additional "direct" srcs
if needed.
def _merge_proto_infos(ctx, workspace_root, descriptor_set, srcs = [], deps = []):
"""Merge multiple ProtoInfo providers into one."""
# Symlink the proto files relative to the workspace root
virtual_srcs = [src for src in srcs]
for dep in deps:
if ProtoInfo in dep:
for src in dep[ProtoInfo].transitive_sources.to_list():
rel_path = "/".join(src.path.split("/")[-3:]) # <--- specific to my interfaces
if workspace_root != "":
rel_path = "{}/{}".format(workspace_root, rel_path)
virtual_src = ctx.actions.declare_file(rel_path)
ctx.actions.symlink(output = virtual_src, target_file = src)
virtual_srcs.append(virtual_src)
# Create the provider. Note the deps are gone.
proto_info = ProtoInfo(
srcs = virtual_srcs,
deps = [],
descriptor_set = descriptor_set,
workspace_root = workspace_root,
)
# Generate the descriptor_set for the ProtoInfo provider.
proto_common.compile(
actions = ctx.actions,
proto_info = proto_info,
proto_lang_toolchain_info = ctx.toolchains["@protobuf//bazel/private:proto_toolchain_type"].proto,
generated_files = [descriptor_set],
)
# Return the merged provider.
return proto_info
Describe alternatives you've considered
I can't find any other way of achieving my goal. I would prefer a solution where (1) protobuf
functions when the transitive deps have different workspace roots, (2) the protobuf
rules work when ProtoInfo
providers have only transitive deps and no direct sources or descriptor sets, and (3) that there is a function proto_common.merge_proto_infos
that enables you to correctly merge lists or depsets of ProtoInfo
.
Additional context
Link to a branch containing my code above:
https://github.com/asymingt/bazel_ros_demo/blob/278ca9ff335c0f1bd266e24d62a2fbbd8176f377/modules/rosidl_adapter_proto/defs.bzl#L23-L55