|
|
|
"""Utility functions for generating protobuf code."""
|
|
|
|
|
|
|
|
_PROTO_EXTENSION = ".proto"
|
|
|
|
|
|
|
|
def well_known_proto_libs():
|
|
|
|
return [
|
|
|
|
"@com_google_protobuf//:any_proto",
|
|
|
|
"@com_google_protobuf//:api_proto",
|
|
|
|
"@com_google_protobuf//:compiler_plugin_proto",
|
|
|
|
"@com_google_protobuf//:descriptor_proto",
|
|
|
|
"@com_google_protobuf//:duration_proto",
|
|
|
|
"@com_google_protobuf//:empty_proto",
|
|
|
|
"@com_google_protobuf//:field_mask_proto",
|
|
|
|
"@com_google_protobuf//:source_context_proto",
|
|
|
|
"@com_google_protobuf//:struct_proto",
|
|
|
|
"@com_google_protobuf//:timestamp_proto",
|
|
|
|
"@com_google_protobuf//:type_proto",
|
|
|
|
"@com_google_protobuf//:wrappers_proto",
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_proto_root(workspace_root):
|
|
|
|
"""Gets the root protobuf directory.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
workspace_root: context.label.workspace_root
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The directory relative to which generated include paths should be.
|
|
|
|
"""
|
|
|
|
if workspace_root:
|
|
|
|
return "/{}".format(workspace_root)
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def _strip_proto_extension(proto_filename):
|
|
|
|
if not proto_filename.endswith(_PROTO_EXTENSION):
|
|
|
|
fail('"{}" does not end with "{}"'.format(
|
|
|
|
proto_filename,
|
|
|
|
_PROTO_EXTENSION,
|
|
|
|
))
|
|
|
|
return proto_filename[:-len(_PROTO_EXTENSION)]
|
|
|
|
|
|
|
|
def proto_path_to_generated_filename(proto_path, fmt_str):
|
|
|
|
"""Calculates the name of a generated file for a protobuf path.
|
|
|
|
|
|
|
|
For example, "examples/protos/helloworld.proto" might map to
|
|
|
|
"helloworld.pb.h".
|
|
|
|
|
|
|
|
Args:
|
|
|
|
proto_path: The path to the .proto file.
|
|
|
|
fmt_str: A format string used to calculate the generated filename. For
|
|
|
|
example, "{}.pb.h" might be used to calculate a C++ header filename.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The generated filename.
|
|
|
|
"""
|
|
|
|
return fmt_str.format(_strip_proto_extension(proto_path))
|
|
|
|
|
|
|
|
def _get_include_directory(include):
|
|
|
|
directory = include.path
|
|
|
|
prefix_len = 0
|
|
|
|
|
|
|
|
virtual_imports = "/_virtual_imports/"
|
|
|
|
if not include.is_source and virtual_imports in include.path:
|
|
|
|
root, relative = include.path.split(virtual_imports, 2)
|
|
|
|
result = root + virtual_imports + relative.split("/", 1)[0]
|
|
|
|
return result
|
|
|
|
|
|
|
|
if not include.is_source and directory.startswith(include.root.path):
|
|
|
|
prefix_len = len(include.root.path) + 1
|
|
|
|
|
|
|
|
if directory.startswith("external", prefix_len):
|
|
|
|
external_separator = directory.find("/", prefix_len)
|
|
|
|
repository_separator = directory.find("/", external_separator + 1)
|
|
|
|
return directory[:repository_separator]
|
|
|
|
else:
|
|
|
|
return include.root.path if include.root.path else "."
|
|
|
|
|
|
|
|
def get_include_protoc_args(includes):
|
|
|
|
"""Returns protoc args that imports protos relative to their import root.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
includes: A list of included proto files.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A list of arguments to be passed to protoc. For example, ["--proto_path=."].
|
|
|
|
"""
|
|
|
|
return [
|
|
|
|
"--proto_path={}".format(_get_include_directory(include))
|
|
|
|
for include in includes
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_plugin_args(plugin, flags, dir_out, generate_mocks):
|
|
|
|
"""Returns arguments configuring protoc to use a plugin for a language.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
plugin: An executable file to run as the protoc plugin.
|
|
|
|
flags: The plugin flags to be passed to protoc.
|
|
|
|
dir_out: The output directory for the plugin.
|
|
|
|
generate_mocks: A bool indicating whether to generate mocks.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A list of protoc arguments configuring the plugin.
|
|
|
|
"""
|
|
|
|
augmented_flags = list(flags)
|
|
|
|
if generate_mocks:
|
|
|
|
augmented_flags.append("generate_mock_code=true")
|
|
|
|
return [
|
|
|
|
"--plugin=protoc-gen-PLUGIN=" + plugin.path,
|
|
|
|
"--PLUGIN_out=" + ",".join(augmented_flags) + ":" + dir_out,
|
|
|
|
]
|
|
|
|
|
|
|
|
def _get_staged_proto_file(context, source_file):
|
|
|
|
if source_file.dirname == context.label.package:
|
|
|
|
return source_file
|
|
|
|
else:
|
|
|
|
copied_proto = context.actions.declare_file(source_file.basename)
|
|
|
|
context.actions.run_shell(
|
|
|
|
inputs = [source_file],
|
|
|
|
outputs = [copied_proto],
|
|
|
|
command = "cp {} {}".format(source_file.path, copied_proto.path),
|
|
|
|
mnemonic = "CopySourceProto",
|
|
|
|
)
|
|
|
|
return copied_proto
|
|
|
|
|
|
|
|
|
|
|
|
def protos_from_context(context):
|
|
|
|
"""Copies proto files to the appropriate location.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
context: The ctx object for the rule.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A list of the protos.
|
|
|
|
"""
|
|
|
|
protos = []
|
|
|
|
for src in context.attr.deps:
|
|
|
|
for file in src[ProtoInfo].direct_sources:
|
|
|
|
protos.append(_get_staged_proto_file(context, file))
|
|
|
|
return protos
|
|
|
|
|
|
|
|
|
|
|
|
def includes_from_deps(deps):
|
|
|
|
"""Get includes from rule dependencies."""
|
|
|
|
return [
|
|
|
|
file
|
|
|
|
for src in deps
|
|
|
|
for file in src[ProtoInfo].transitive_imports.to_list()
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_proto_arguments(protos, genfiles_dir_path):
|
|
|
|
"""Get the protoc arguments specifying which protos to compile."""
|
|
|
|
arguments = []
|
|
|
|
for proto in protos:
|
|
|
|
massaged_path = proto.path
|
|
|
|
if massaged_path.startswith(genfiles_dir_path):
|
|
|
|
massaged_path = proto.path[len(genfiles_dir_path) + 1:]
|
|
|
|
arguments.append(massaged_path)
|
|
|
|
return arguments
|
|
|
|
|
|
|
|
def declare_out_files(protos, context, generated_file_format):
|
|
|
|
"""Declares and returns the files to be generated."""
|
|
|
|
return [
|
|
|
|
context.actions.declare_file(
|
|
|
|
proto_path_to_generated_filename(
|
|
|
|
proto.basename,
|
|
|
|
generated_file_format,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
for proto in protos
|
|
|
|
]
|