Merge pull request #20846 from vam-google/master

[bazel] Add an ability to call an optional custom plugin for py_proto_library and py_grpc_library
reviewable/pr20638/r3
Richard Belleville 5 years ago committed by GitHub
commit 8b6e2ab3d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      bazel/protobuf.bzl
  2. 47
      bazel/python_rules.bzl
  3. 10
      bazel/test/python_test_repo/BUILD
  4. 37
      bazel/test/python_test_repo/dummy_plugin.py

@ -89,7 +89,12 @@ def get_include_directory(source_file):
else: else:
return source_file.root.path if source_file.root.path else "." return source_file.root.path if source_file.root.path else "."
def get_plugin_args(plugin, flags, dir_out, generate_mocks): def get_plugin_args(
plugin,
flags,
dir_out,
generate_mocks,
plugin_name = "PLUGIN"):
"""Returns arguments configuring protoc to use a plugin for a language. """Returns arguments configuring protoc to use a plugin for a language.
Args: Args:
@ -97,16 +102,28 @@ def get_plugin_args(plugin, flags, dir_out, generate_mocks):
flags: The plugin flags to be passed to protoc. flags: The plugin flags to be passed to protoc.
dir_out: The output directory for the plugin. dir_out: The output directory for the plugin.
generate_mocks: A bool indicating whether to generate mocks. generate_mocks: A bool indicating whether to generate mocks.
plugin_name: A name of the plugin, it is required to be unique when there
are more than one plugin used in a single protoc command.
Returns: Returns:
A list of protoc arguments configuring the plugin. A list of protoc arguments configuring the plugin.
""" """
augmented_flags = list(flags) augmented_flags = list(flags)
if generate_mocks: if generate_mocks:
augmented_flags.append("generate_mock_code=true") augmented_flags.append("generate_mock_code=true")
augmented_dir_out = dir_out
if augmented_flags:
augmented_dir_out = ",".join(augmented_flags) + ":" + dir_out
return [ return [
"--plugin=protoc-gen-PLUGIN=" + plugin.path, "--plugin=protoc-gen-{plugin_name}={plugin_path}".format(
"--PLUGIN_out=" + ",".join(augmented_flags) + ":" + dir_out, plugin_name = plugin_name,
plugin_path = plugin.path,
),
"--{plugin_name}_out={dir_out}".format(
plugin_name = plugin_name,
dir_out = augmented_dir_out,
)
] ]
def _get_staged_proto_file(context, source_file): def _get_staged_proto_file(context, source_file):

@ -29,6 +29,16 @@ def _generate_py_impl(context):
] + [ ] + [
"--proto_path={}".format(context.genfiles_dir.path), "--proto_path={}".format(context.genfiles_dir.path),
]) ])
if context.attr.plugin:
arguments += get_plugin_args(
context.executable.plugin,
[],
out_dir.path,
False,
context.attr.plugin.label.name
)
tools.append(context.executable.plugin)
arguments += get_proto_arguments(protos, context.genfiles_dir.path) arguments += get_proto_arguments(protos, context.genfiles_dir.path)
context.actions.run( context.actions.run(
@ -59,6 +69,12 @@ _generate_pb2_src = rule(
allow_empty = False, allow_empty = False,
providers = [ProtoInfo], providers = [ProtoInfo],
), ),
"plugin": attr.label(
mandatory = False,
executable = True,
providers = ["files_to_run"],
cfg = "host",
),
"_protoc": attr.label( "_protoc": attr.label(
default = Label("//external:protocol_compiler"), default = Label("//external:protocol_compiler"),
providers = ["files_to_run"], providers = ["files_to_run"],
@ -72,12 +88,17 @@ _generate_pb2_src = rule(
def py_proto_library( def py_proto_library(
name, name,
deps, deps,
plugin = None,
**kwargs): **kwargs):
"""Generate python code for a protobuf. """Generate python code for a protobuf.
Args: Args:
name: The name of the target. name: The name of the target.
deps: A list of proto_library dependencies. Must contain a single element. deps: A list of proto_library dependencies. Must contain a single element.
plugin: An optional custom protoc plugin to execute together with
generating the protobuf code.
**kwargs: Additional arguments to be supplied to the invocation of
py_library.
""" """
codegen_target = "_{}_codegen".format(name) codegen_target = "_{}_codegen".format(name)
if len(deps) != 1: if len(deps) != 1:
@ -87,6 +108,7 @@ def py_proto_library(
_generate_pb2_src( _generate_pb2_src(
name = codegen_target, name = codegen_target,
deps = deps, deps = deps,
plugin = plugin,
**kwargs **kwargs
) )
@ -108,14 +130,23 @@ def _generate_pb2_grpc_src_impl(context):
plugin_flags = ["grpc_2_0"] + context.attr.strip_prefixes plugin_flags = ["grpc_2_0"] + context.attr.strip_prefixes
arguments = [] arguments = []
tools = [context.executable._protoc, context.executable._plugin] tools = [context.executable._protoc, context.executable._grpc_plugin]
out_dir = get_out_dir(protos, context) out_dir = get_out_dir(protos, context)
arguments += get_plugin_args( arguments += get_plugin_args(
context.executable._plugin, context.executable._grpc_plugin,
plugin_flags, plugin_flags,
out_dir.path, out_dir.path,
False, False,
) )
if context.attr.plugin:
arguments += get_plugin_args(
context.executable.plugin,
[],
out_dir.path,
False,
context.attr.plugin.label.name
)
tools.append(context.executable.plugin)
arguments += [ arguments += [
"--proto_path={}".format(get_include_directory(i)) "--proto_path={}".format(get_include_directory(i))
@ -153,7 +184,13 @@ _generate_pb2_grpc_src = rule(
providers = [ProtoInfo], providers = [ProtoInfo],
), ),
"strip_prefixes": attr.string_list(), "strip_prefixes": attr.string_list(),
"_plugin": attr.label( "plugin": attr.label(
mandatory = False,
executable = True,
providers = ["files_to_run"],
cfg = "host",
),
"_grpc_plugin": attr.label(
executable = True, executable = True,
providers = ["files_to_run"], providers = ["files_to_run"],
cfg = "host", cfg = "host",
@ -173,6 +210,7 @@ def py_grpc_library(
name, name,
srcs, srcs,
deps, deps,
plugin = None,
strip_prefixes = [], strip_prefixes = [],
**kwargs): **kwargs):
"""Generate python code for gRPC services defined in a protobuf. """Generate python code for gRPC services defined in a protobuf.
@ -187,6 +225,8 @@ def py_grpc_library(
stripped from the beginning of foo_pb2 modules imported by the stripped from the beginning of foo_pb2 modules imported by the
generated stubs. This is useful in combination with the `imports` generated stubs. This is useful in combination with the `imports`
attribute of the `py_library` rule. attribute of the `py_library` rule.
plugin: An optional custom protoc plugin to execute together with
generating the gRPC code.
**kwargs: Additional arguments to be supplied to the invocation of **kwargs: Additional arguments to be supplied to the invocation of
py_library. py_library.
""" """
@ -201,6 +241,7 @@ def py_grpc_library(
name = codegen_grpc_target, name = codegen_grpc_target,
deps = srcs, deps = srcs,
strip_prefixes = strip_prefixes, strip_prefixes = strip_prefixes,
plugin = plugin,
**kwargs **kwargs
) )

@ -79,9 +79,11 @@ proto_library(
strip_import_prefix = "" strip_import_prefix = ""
) )
# Also test the custom plugin execution parameter
py_proto_library( py_proto_library(
name = "helloworld_moved_py_pb2", name = "helloworld_moved_py_pb2",
deps = [":helloworld_moved_proto"], deps = [":helloworld_moved_proto"],
plugin = ":dummy_plugin"
) )
py_grpc_library( py_grpc_library(
@ -101,3 +103,11 @@ py2and3_test(
":timestamp_py_pb2", ":timestamp_py_pb2",
], ],
) )
py_binary(
name = "dummy_plugin",
srcs = [":dummy_plugin.py"],
deps = [
"@com_google_protobuf//:protobuf_python",
],
)

@ -0,0 +1,37 @@
# Copyright 2019 the gRPC authors.
#
# 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.
"""A dummy plugin for testing"""
import sys
from google.protobuf.compiler.plugin_pb2 import CodeGeneratorRequest
from google.protobuf.compiler.plugin_pb2 import CodeGeneratorResponse
def main(input_file=sys.stdin, output_file=sys.stdout):
request = CodeGeneratorRequest.FromString(input_file.buffer.read())
answer = []
for fname in request.file_to_generate:
answer.append(CodeGeneratorResponse.File(
name=fname.replace('.proto', '_pb2.py'),
insertion_point='module_scope',
content="# Hello {}, I'm a dummy plugin!".format(fname),
))
cgr = CodeGeneratorResponse(file=answer)
output_file.buffer.write(cgr.SerializeToString())
if __name__ == '__main__':
main()
Loading…
Cancel
Save