build/bazel/grpc_gateway.bzl (159 lines of code) (raw):
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""grpc_gateway provides bazel rules for dealing with grpc-gateway.
We use grpc-gateway (https://github.com/grpc-ecosystem/grpc-gateway) to generate
json-grpc proxies but cannot use the generated file as-is. generate_grpc_gateway
runs the grpc-gateway generator, then applies patches to the generate file. For
more details about the patches applied and why they are required, see comments
in fix_grpc_gateway.go in this directory.
"""
load("@io_bazel_rules_go//go:def.bzl", "go_library")
def _generate_grpc_gateway_file_impl(ctx):
# gen-grpc-gateway generates the output file in the same directory as the
# proto file from which contained the service. This output directory is not
# controllable and we need to infer it. Assume that it is the same as the
# directory of the first direct source.
proto_info = ctx.attr.src[ProtoInfo]
proto_dir = proto_info.direct_sources[0].dirname
proto_root = proto_info.direct_sources[0].owner.workspace_root
if proto_root:
proto_dir = proto_dir[len(proto_root) + 1:]
original_genfile = ctx.actions.declare_file(
proto_dir + "/" + ctx.attr.out_filename,
)
# We will output the modified gateway file in the package directory.
modified_genfile = ctx.actions.declare_file(ctx.attr.out_filename)
# Collect include paths for transitive sources.
include_paths = {}
for src in proto_info.transitive_proto_path.to_list():
include_paths[src] = True
# Setup the arguments for the grpc_gateway plugin.
plugin_args = []
# Generate the output file with the same directory structure as the input
# file. By default, the output file is generated in a directory whose
# structure follows the import path for the go proto package which we cannot
# introspect easily. Using source_relative allows us to predict the output
# file path which is needed for bazel. For more information on this
# argument, see https://github.com/golang/protobuf#packages-and-input-paths.
plugin_args.append("paths=source_relative")
# Add an optional yaml config override to the generator. These override the
# proto http annotations defined on the service in the input proto file. For
# more information, see the comments in the gateway/*.yaml files.
if ctx.files.yaml_config:
plugin_args.append(
"grpc_api_configuration=" + ctx.files.yaml_config[0].path,
)
# First generate the gateway file via gen-grpc-gateway.
args = ctx.actions.args()
for include_path in include_paths:
args.add("-I", include_path)
args.add(
"--plugin",
ctx.expand_location(
"$(location %s)" % ctx.attr.protoc_gen_grpc_gateway.label,
targets = [ctx.attr.protoc_gen_grpc_gateway],
),
)
args.add(
"--grpc-gateway_out",
",".join(plugin_args) + ":" + modified_genfile.dirname,
)
args.add_all([x.path for x in proto_info.direct_sources])
ctx.actions.run(
inputs = proto_info.transitive_sources.to_list() + ctx.files.yaml_config,
outputs = [original_genfile],
executable = ctx.executable.protoc,
arguments = [args],
tools = [ctx.executable.protoc_gen_grpc_gateway],
)
# Now fix-up the file so we can use it as a standalone package.
args = ctx.actions.args()
args.add("--input_gateway_file", original_genfile.path)
args.add("--output_gateway_file", modified_genfile.path)
args.add("--proto_imports", ",".join(ctx.attr.proto_imports))
ctx.actions.run(
inputs = [original_genfile],
outputs = [modified_genfile],
executable = ctx.executable.fix_grpc_gateway,
arguments = [args],
)
GRPC_GATEWAY_PKG = "@grpc_ecosystem_grpc_gateway//protoc-gen-grpc-gateway"
GRPC_GATEWAY_BIN = "protoc-gen-grpc-gateway"
generate_grpc_gateway_file = rule(
attrs = {
"src": attr.label(
providers = [ProtoInfo],
mandatory = True,
),
"proto_imports": attr.string_list(),
"package_name": attr.string(),
"out_filename": attr.string(),
"yaml_config": attr.label(allow_files = True),
"protoc": attr.label(
executable = True,
cfg = "exec",
default = "@com_google_protobuf//:protoc",
),
"protoc_gen_grpc_gateway": attr.label(
executable = True,
cfg = "exec",
default = GRPC_GATEWAY_PKG + ":" + GRPC_GATEWAY_BIN,
),
"fix_grpc_gateway": attr.label(
executable = True,
cfg = "exec",
default = ":fix_grpc_gateway",
),
},
outputs = {
"out": "%{out_filename}",
},
implementation = _generate_grpc_gateway_file_impl,
)
def generate_grpc_gateway(
name,
src,
proto_imports,
out_filename,
deps,
yaml_config = None):
generate_grpc_gateway_file(
name = name + "_gen",
package_name = name,
src = src,
proto_imports = proto_imports,
out_filename = out_filename,
yaml_config = yaml_config,
)
go_library(
importpath = "cloud_spanner_emulator/gateway/" + name,
name = name,
srcs = [":" + name + "_gen"],
deps = deps + [
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//metadata",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//grpclog:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@grpc_ecosystem_grpc_gateway//runtime:go_default_library",
"@grpc_ecosystem_grpc_gateway//utilities:go_default_library",
"@com_github_golang_protobuf//descriptor:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//proto",
"@org_golang_google_protobuf//encoding/protojson:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)