blob: 2cb1f22fddaf4fd358baa2d0632ba0a5b2d931f9 [file] [log] [blame]
"""Generated an open-api spec for a grpc api spec.
Reads the the api spec in protobuf format and generate an open-api spec.
Optionally applies settings from the grpc-service configuration.
"""
load("@rules_proto//proto:defs.bzl", "ProtoInfo")
# TODO(yannic): Replace with |proto_common.direct_source_infos| when
# https://github.com/bazelbuild/rules_proto/pull/22 lands.
def _direct_source_infos(proto_info, provided_sources = []):
"""Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources.
Files that are both in `proto_info`'s direct sources and in
`provided_sources` are skipped. This is useful, e.g., for well-known
protos that are already provided by the Protobuf runtime.
Args:
proto_info: An instance of `ProtoInfo`.
provided_sources: Optional. A sequence of files to ignore.
Usually, these files are already provided by the
Protocol Buffer runtime (e.g. Well-Known protos).
Returns: A sequence of `ProtoFileInfo` containing information about
`proto_info`'s direct sources.
"""
source_root = proto_info.proto_source_root
if "." == source_root:
return [struct(file = src, import_path = src.path) for src in proto_info.direct_sources]
offset = len(source_root) + 1 # + '/'.
infos = []
for src in proto_info.direct_sources:
# TODO(yannic): Remove this hack when we drop support for Bazel < 1.0.
local_offset = offset
if src.root.path and not source_root.startswith(src.root.path):
# Before Bazel 1.0, `proto_source_root` wasn't guaranteed to be a
# prefix of `src.path`. This could happend, e.g., if `file` was
# generated (https://github.com/bazelbuild/bazel/issues/9215).
local_offset += len(src.root.path) + 1 # + '/'.
infos.append(struct(file = src, import_path = src.path[local_offset:]))
return infos
def _run_proto_gen_swagger(
actions,
proto_info,
target_name,
transitive_proto_srcs,
protoc,
protoc_gen_swagger,
grpc_api_configuration,
single_output,
json_names_for_fields):
args = actions.args()
args.add("--plugin", "protoc-gen-swagger=%s" % protoc_gen_swagger.path)
args.add("--swagger_opt", "logtostderr=true")
args.add("--swagger_opt", "allow_repeated_fields_in_body=true")
extra_inputs = []
if grpc_api_configuration:
extra_inputs.append(grpc_api_configuration)
args.add("--swagger_opt", "grpc_api_configuration=%s" % grpc_api_configuration.path)
if json_names_for_fields:
args.add("--swagger_opt", "json_names_for_fields=true")
proto_file_infos = _direct_source_infos(proto_info)
# TODO(yannic): Use |proto_info.transitive_descriptor_sets| when
# https://github.com/bazelbuild/bazel/issues/9337 is fixed.
args.add_all(proto_info.transitive_proto_path, format_each = "--proto_path=%s")
if single_output:
args.add("--swagger_opt", "allow_merge=true")
args.add("--swagger_opt", "merge_file_name=%s" % target_name)
swagger_file = actions.declare_file("%s.swagger.json" % target_name)
args.add("--swagger_out", swagger_file.dirname)
args.add_all([f.import_path for f in proto_file_infos])
actions.run(
executable = protoc,
tools = [protoc_gen_swagger],
inputs = depset(
direct = extra_inputs,
transitive = [transitive_proto_srcs],
),
outputs = [swagger_file],
arguments = [args],
)
return [swagger_file]
# TODO(yannic): We may be able to generate all files in a single action,
# but that will change at least the semantics of `use_go_template.proto`.
swagger_files = []
for proto_file_info in proto_file_infos:
# TODO(yannic): This probably doesn't work as expected: we only add this
# option after we have seen it, so `.proto` sources that happen to be
# in the list of `.proto` files before `use_go_template.proto` will be
# compiled without this option, and all sources that get compiled after
# `use_go_template.proto` will have this option on.
if proto_file_info.file.basename == "use_go_template.proto":
args.add("--swagger_opt", "use_go_templates=true")
file_name = "%s.swagger.json" % proto_file_info.import_path[:-len(".proto")]
swagger_file = actions.declare_file(
"_virtual_imports/%s/%s" % (target_name, file_name),
)
file_args = actions.args()
offset = len(file_name) + 1 # + '/'.
file_args.add("--swagger_out", swagger_file.path[:-offset])
file_args.add(proto_file_info.import_path)
actions.run(
executable = protoc,
tools = [protoc_gen_swagger],
inputs = depset(
direct = extra_inputs,
transitive = [transitive_proto_srcs],
),
outputs = [swagger_file],
arguments = [args, file_args],
)
swagger_files.append(swagger_file)
return swagger_files
def _proto_gen_swagger_impl(ctx):
proto = ctx.attr.proto[ProtoInfo]
return [
DefaultInfo(
files = depset(
_run_proto_gen_swagger(
actions = ctx.actions,
proto_info = proto,
target_name = ctx.attr.name,
transitive_proto_srcs = depset(
direct = ctx.files._well_known_protos,
transitive = [proto.transitive_sources],
),
protoc = ctx.executable._protoc,
protoc_gen_swagger = ctx.executable._protoc_gen_swagger,
grpc_api_configuration = ctx.file.grpc_api_configuration,
single_output = ctx.attr.single_output,
json_names_for_fields = ctx.attr.json_names_for_fields,
),
),
),
]
protoc_gen_swagger = rule(
attrs = {
"proto": attr.label(
mandatory = True,
providers = [ProtoInfo],
),
"grpc_api_configuration": attr.label(
allow_single_file = True,
mandatory = False,
),
"single_output": attr.bool(
default = False,
mandatory = False,
),
"json_names_for_fields": attr.bool(
default = False,
mandatory = False,
),
"_protoc": attr.label(
default = "@com_google_protobuf//:protoc",
executable = True,
cfg = "host",
),
"_well_known_protos": attr.label(
default = "@com_google_protobuf//:well_known_protos",
allow_files = True,
),
"_protoc_gen_swagger": attr.label(
default = Label("//protoc-gen-swagger:protoc-gen-swagger"),
executable = True,
cfg = "host",
),
},
implementation = _proto_gen_swagger_impl,
)