@ -21,42 +21,62 @@ load(
" get_out_dir " ,
" get_plugin_args " ,
" get_proto_arguments " ,
" get_staged_proto_file " ,
" includes_from_deps " ,
" is_in_virtual_imports " ,
" is_well_known " ,
" protos_from_context " ,
)
_GENERATED_PROTO_FORMAT = " {} _pb2.py "
_GENERATED_GRPC_PROTO_FORMAT = " {} _pb2_grpc.py "
def _generate_py_impl ( context ) :
protos = protos_from_context ( context )
includes = includes_from_deps ( context . attr . deps )
PyProtoInfo = provider (
" The Python outputs from the Protobuf compiler. " ,
fields = {
" py_info " : " A PyInfo provider for the generated code. " ,
" generated_py_srcs " : " The direct (not transitive) generated Python source files. " ,
} ,
)
def _merge_pyinfos ( pyinfos ) :
return PyInfo (
transitive_sources = depset ( transitive = [ p . transitive_sources for p in pyinfos ] ) ,
imports = depset ( transitive = [ p . imports for p in pyinfos ] ) ,
)
def _gen_py_aspect_impl ( target , context ) :
# Early return for well-known protos.
if is_well_known ( str ( context . label ) ) :
return [
PyProtoInfo ( py_info = context . attr . _protobuf_library [ PyInfo ] ) ,
]
protos = [ ]
for p in target [ ProtoInfo ] . direct_sources :
protos . append ( get_staged_proto_file ( target . label , context , p ) )
includes = depset ( direct = protos , transitive = [ target [ ProtoInfo ] . transitive_imports ] )
out_files = declare_out_files ( protos , context , _GENERATED_PROTO_FORMAT )
generated_py_srcs = out_files
tools = [ context . executable . _protoc ]
out_dir = get_out_dir ( protos , context )
arguments = ( [
" --python_out= {} " . format ( out_dir . path ) ,
] + [
" --proto_path= {} " . format ( get_include_directory ( i ) )
for i in includes
for i in includes . to_list ( )
] + [
" --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 )
context . actions . run (
inputs = protos + includes ,
inputs = protos + includes . to_list ( ) ,
tools = tools ,
outputs = out_files ,
executable = context . executable . _protoc ,
@ -68,26 +88,77 @@ def _generate_py_impl(context):
if out_dir . import_path :
imports . append ( " %s / %s / %s " % ( context . workspace_name , context . label . package , out_dir . import_path ) )
py_info = PyInfo ( transitive_sources = depset ( direct = out_files ) , imports = depset ( direct = imports ) )
return PyProtoInfo (
py_info = _merge_pyinfos (
[
py_info ,
context . attr . _protobuf_library [ PyInfo ] ,
] + [ dep [ PyProtoInfo ] . py_info for dep in context . rule . attr . deps ] ,
) ,
generated_py_srcs = generated_py_srcs ,
)
_gen_py_aspect = aspect (
implementation = _gen_py_aspect_impl ,
attr_aspects = [ " deps " ] ,
fragments = [ " py " ] ,
attrs = {
" _protoc " : attr . label (
default = Label ( " //external:protocol_compiler " ) ,
providers = [ " files_to_run " ] ,
executable = True ,
cfg = " host " ,
) ,
" _protobuf_library " : attr . label (
default = Label ( " @com_google_protobuf//:protobuf_python " ) ,
providers = [ PyInfo ] ,
) ,
} ,
)
def _generate_py_impl ( context ) :
if ( len ( context . attr . deps ) != 1 ) :
fail ( " Can only compile a single proto at a time. " )
py_sources = [ ]
# If the proto_library this rule *directly* depends on is in another
# package, then we generate .py files to import them in this package. This
# behavior is needed to allow rearranging of import paths to make Bazel
# outputs align with native python workflows.
#
# Note that this approach is vulnerable to protoc defining __all__ or other
# symbols with __ prefixes that need to be directly imported. Since these
# names are likely to be reserved for private APIs, the risk is minimal.
if context . label . package != context . attr . deps [ 0 ] . label . package :
for py_src in context . attr . deps [ 0 ] [ PyProtoInfo ] . generated_py_srcs :
reimport_py_file = context . actions . declare_file ( py_src . basename )
py_sources . append ( reimport_py_file )
import_line = " from %s import * " % py_src . short_path . replace ( " / " , " . " ) [ : - len ( " .py " ) ]
context . actions . write ( reimport_py_file , import_line )
# Collect output PyInfo provider.
imports = [ context . label . package + " / " + i for i in context . attr . imports ]
py_info = PyInfo ( transitive_sources = depset ( direct = py_sources ) , imports = depset ( direct = imports ) )
out_pyinfo = _merge_pyinfos ( [ py_info , context . attr . deps [ 0 ] [ PyProtoInfo ] . py_info ] )
runfiles = context . runfiles ( files = out_pyinfo . transitive_sources . to_list ( ) ) . merge ( context . attr . _protobuf_library [ DefaultInfo ] . data_runfiles )
return [
DefaultInfo ( files = depset ( direct = out_files ) ) ,
PyInfo (
transitive_sources = depset ( ) ,
imports = depset ( direct = imports ) ,
DefaultInfo (
files = out_pyinfo . transitive_sources ,
runfiles = runfiles ,
) ,
out_pyinfo ,
]
_generate_pb2_src = rule (
py_proto_library = rule (
attrs = {
" deps " : attr . label_list (
mandatory = True ,
allow_empty = False ,
providers = [ ProtoInfo ] ,
) ,
" plugin " : attr . label (
mandatory = False ,
executable = True ,
providers = [ " files_to_run " ] ,
cfg = " host " ,
aspects = [ _gen_py_aspect ] ,
) ,
" _protoc " : attr . label (
default = Label ( " //external:protocol_compiler " ) ,
@ -95,46 +166,15 @@ _generate_pb2_src = rule(
executable = True ,
cfg = " host " ,
) ,
" _protobuf_library " : attr . label (
default = Label ( " @com_google_protobuf//:protobuf_python " ) ,
providers = [ PyInfo ] ,
) ,
" imports " : attr . string_list ( ) ,
} ,
implementation = _generate_py_impl ,
)
def py_proto_library (
name ,
deps ,
plugin = None ,
* * kwargs ) :
""" Generate python code for a protobuf.
Args :
name : The name of the target .
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 )
if len ( deps ) != 1 :
fail ( " Can only compile a single proto at a time. " )
_generate_pb2_src (
name = codegen_target ,
deps = deps ,
plugin = plugin ,
* * kwargs
)
native . py_library (
name = name ,
srcs = [ " : {} " . format ( codegen_target ) ] ,
deps = [
" @com_google_protobuf//:protobuf_python " ,
" : {} " . format ( codegen_target ) ,
] ,
* * kwargs
)
def _generate_pb2_grpc_src_impl ( context ) :
protos = protos_from_context ( context )
includes = includes_from_deps ( context . attr . deps )
@ -151,15 +191,6 @@ def _generate_pb2_grpc_src_impl(context):
out_dir . path ,
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 + = [
" --proto_path= {} " . format ( get_include_directory ( i ) )
@ -177,13 +208,22 @@ def _generate_pb2_grpc_src_impl(context):
mnemonic = " ProtocInvocation " ,
)
p = PyInfo ( transitive_sources = depset ( direct = out_files ) )
py_info = _merge_pyinfos (
[
p ,
context . attr . _grpc_library [ PyInfo ] ,
] + [ dep [ PyInfo ] for dep in context . attr . py_deps ] ,
)
runfiles = context . runfiles ( files = out_files , transitive_files = py_info . transitive_sources ) . merge ( context . attr . _grpc_library [ DefaultInfo ] . data_runfiles )
return [
DefaultInfo ( files = depset ( direct = out_files ) ) ,
PyInfo (
transitive_sources = depset ( ) ,
# Imports are already configured by the generated py impl
imports = depset ( ) ,
DefaultInfo (
files = depset ( direct = out_files ) ,
runfiles = runfiles ,
) ,
py_info ,
]
_generate_pb2_grpc_src = rule (
@ -193,13 +233,12 @@ _generate_pb2_grpc_src = rule(
allow_empty = False ,
providers = [ ProtoInfo ] ,
) ,
" strip_prefixes " : attr . string_list ( ) ,
" plugin " : attr . label (
mandatory = False ,
executable = True ,
providers = [ " files_to_run " ] ,
cfg = " host " ,
" py_deps " : attr . label_list (
mandatory = True ,
allow_empty = False ,
providers = [ PyInfo ] ,
) ,
" strip_prefixes " : attr . string_list ( ) ,
" _grpc_plugin " : attr . label (
executable = True ,
providers = [ " files_to_run " ] ,
@ -212,6 +251,10 @@ _generate_pb2_grpc_src = rule(
cfg = " host " ,
default = Label ( " //external:protocol_compiler " ) ,
) ,
" _grpc_library " : attr . label (
default = Label ( " //src/python/grpcio/grpc:grpcio " ) ,
providers = [ PyInfo ] ,
) ,
} ,
implementation = _generate_pb2_grpc_src_impl ,
)
@ -220,7 +263,6 @@ def py_grpc_library(
name ,
srcs ,
deps ,
plugin = None ,
strip_prefixes = [ ] ,
* * kwargs ) :
""" Generate python code for gRPC services defined in a protobuf.
@ -235,12 +277,9 @@ def py_grpc_library(
stripped from the beginning of foo_pb2 modules imported by the
generated stubs . This is useful in combination with the ` imports `
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
py_library .
"""
codegen_grpc_target = " _ {} _grpc_codegen " . format ( name )
if len ( srcs ) != 1 :
fail ( " Can only compile a single proto at a time. " )
@ -248,23 +287,10 @@ def py_grpc_library(
fail ( " Deps must have length 1. " )
_generate_pb2_grpc_src (
name = codegen_grpc_target ,
name = name ,
deps = srcs ,
py_deps = deps ,
strip_prefixes = strip_prefixes ,
plugin = plugin ,
* * kwargs
)
native . py_library (
name = name ,
srcs = [
" : {} " . format ( codegen_grpc_target ) ,
] ,
deps = [
Label ( " //src/python/grpcio/grpc:grpcio " ) ,
] + deps + [
" : {} " . format ( codegen_grpc_target ) ,
] ,
* * kwargs
)