python_rules.bzl 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. """Generates and compiles Python gRPC stubs from proto_library rules."""
  2. load(
  3. "//bazel:protobuf.bzl",
  4. "get_include_protoc_args",
  5. "get_plugin_args",
  6. "get_proto_root",
  7. "proto_path_to_generated_filename",
  8. )
  9. _GENERATED_PROTO_FORMAT = "{}_pb2.py"
  10. _GENERATED_GRPC_PROTO_FORMAT = "{}_pb2_grpc.py"
  11. def _get_staged_proto_file(context, source_file):
  12. if source_file.dirname == context.label.package:
  13. return source_file
  14. else:
  15. copied_proto = context.actions.declare_file(source_file.basename)
  16. context.actions.run_shell(
  17. inputs = [source_file],
  18. outputs = [copied_proto],
  19. command = "cp {} {}".format(source_file.path, copied_proto.path),
  20. mnemonic = "CopySourceProto",
  21. )
  22. return copied_proto
  23. def _generate_py_impl(context):
  24. protos = []
  25. for src in context.attr.deps:
  26. for file in src[ProtoInfo].direct_sources:
  27. protos.append(_get_staged_proto_file(context, file))
  28. includes = [
  29. file
  30. for src in context.attr.deps
  31. for file in src[ProtoInfo].transitive_imports.to_list()
  32. ]
  33. proto_root = get_proto_root(context.label.workspace_root)
  34. out_files = [
  35. context.actions.declare_file(
  36. proto_path_to_generated_filename(
  37. proto.basename,
  38. _GENERATED_PROTO_FORMAT,
  39. ),
  40. )
  41. for proto in protos
  42. ]
  43. tools = [context.executable._protoc]
  44. arguments = ([
  45. "--python_out={}".format(
  46. context.genfiles_dir.path,
  47. ),
  48. ] + get_include_protoc_args(includes) + [
  49. "--proto_path={}".format(context.genfiles_dir.path)
  50. for proto in protos
  51. ])
  52. for proto in protos:
  53. massaged_path = proto.path
  54. if massaged_path.startswith(context.genfiles_dir.path):
  55. massaged_path = proto.path[len(context.genfiles_dir.path) + 1:]
  56. arguments.append(massaged_path)
  57. context.actions.run(
  58. inputs = protos + includes,
  59. tools = tools,
  60. outputs = out_files,
  61. executable = context.executable._protoc,
  62. arguments = arguments,
  63. mnemonic = "ProtocInvocation",
  64. )
  65. return struct(files = depset(out_files))
  66. _generate_pb2_src = rule(
  67. attrs = {
  68. "deps": attr.label_list(
  69. mandatory = True,
  70. allow_empty = False,
  71. providers = [ProtoInfo],
  72. ),
  73. "_protoc": attr.label(
  74. default = Label("//external:protocol_compiler"),
  75. providers = ["files_to_run"],
  76. executable = True,
  77. cfg = "host",
  78. ),
  79. },
  80. implementation = _generate_py_impl,
  81. )
  82. def py_proto_library(
  83. name,
  84. srcs,
  85. **kwargs):
  86. """Generate python code for a protobuf.
  87. Args:
  88. name: The name of the target.
  89. srcs: A list of proto_library dependencies. Must contain a single element.
  90. """
  91. codegen_target = "_{}_codegen".format(name)
  92. if len(srcs) > 1:
  93. fail("Can only compile a single proto at a time.")
  94. _generate_pb2_src(
  95. name = codegen_target,
  96. deps = srcs,
  97. **kwargs
  98. )
  99. native.py_library(
  100. name = name,
  101. srcs = [":{}".format(codegen_target)],
  102. deps = ["@com_google_protobuf//:protobuf_python"],
  103. **kwargs
  104. )
  105. def _generate_pb2_grpc_src_impl(context):
  106. protos = []
  107. for src in context.attr.deps:
  108. for file in src[ProtoInfo].direct_sources:
  109. protos.append(_get_staged_proto_file(context, file))
  110. includes = [
  111. file
  112. for src in context.attr.deps
  113. for file in src[ProtoInfo].transitive_imports.to_list()
  114. ]
  115. proto_root = get_proto_root(context.label.workspace_root)
  116. out_files = [
  117. context.actions.declare_file(
  118. proto_path_to_generated_filename(
  119. proto.basename,
  120. _GENERATED_GRPC_PROTO_FORMAT,
  121. ),
  122. )
  123. for proto in protos
  124. ]
  125. arguments = []
  126. tools = [context.executable._protoc, context.executable._plugin]
  127. arguments += get_plugin_args(
  128. context.executable._plugin,
  129. [],
  130. context.genfiles_dir.path,
  131. False,
  132. )
  133. arguments += get_include_protoc_args(includes)
  134. arguments += [
  135. "--proto_path={}".format(context.genfiles_dir.path)
  136. for proto in protos
  137. ]
  138. for proto in protos:
  139. massaged_path = proto.path
  140. if massaged_path.startswith(context.genfiles_dir.path):
  141. massaged_path = proto.path[len(context.genfiles_dir.path) + 1:]
  142. arguments.append(massaged_path)
  143. context.actions.run(
  144. inputs = protos + includes,
  145. tools = tools,
  146. outputs = out_files,
  147. executable = context.executable._protoc,
  148. arguments = arguments,
  149. mnemonic = "ProtocInvocation",
  150. )
  151. return struct(files = depset(out_files))
  152. _generate_pb2_grpc_src = rule(
  153. attrs = {
  154. "deps": attr.label_list(
  155. mandatory = True,
  156. allow_empty = False,
  157. providers = [ProtoInfo],
  158. ),
  159. "_plugin": attr.label(
  160. executable = True,
  161. providers = ["files_to_run"],
  162. cfg = "host",
  163. default = Label("//src/compiler:grpc_python_plugin"),
  164. ),
  165. "_protoc": attr.label(
  166. executable = True,
  167. providers = ["files_to_run"],
  168. cfg = "host",
  169. default = Label("//external:protocol_compiler"),
  170. ),
  171. },
  172. implementation = _generate_pb2_grpc_src_impl,
  173. )
  174. def py_grpc_library(
  175. name,
  176. srcs,
  177. deps,
  178. **kwargs):
  179. """Generate python code for gRPC services defined in a protobuf.
  180. Args:
  181. name: The name of the target.
  182. srcs: (List of `labels`) a single proto_library target containing the
  183. schema of the service.
  184. deps: (List of `labels`) a single py_proto_library target for the
  185. proto_library in `srcs`.
  186. """
  187. codegen_grpc_target = "_{}_grpc_codegen".format(name)
  188. if len(srcs) > 1:
  189. fail("Can only compile a single proto at a time.")
  190. _generate_pb2_grpc_src(
  191. name = codegen_grpc_target,
  192. deps = srcs,
  193. **kwargs
  194. )
  195. native.py_library(
  196. name = name,
  197. srcs = [
  198. ":{}".format(codegen_grpc_target),
  199. ],
  200. deps = [Label("//src/python/grpcio/grpc:grpcio")] + deps,
  201. **kwargs
  202. )