generate_cc.bzl 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. # Copyright 2021 The gRPC Authors
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Generates C++ grpc stubs from proto_library rules.
  15. This is an internal rule used by cc_grpc_library, and shouldn't be used
  16. directly.
  17. """
  18. load("@rules_proto//proto:defs.bzl", "ProtoInfo")
  19. load(
  20. "//bazel:protobuf.bzl",
  21. "get_include_directory",
  22. "get_plugin_args",
  23. "get_proto_root",
  24. "proto_path_to_generated_filename",
  25. )
  26. _GRPC_PROTO_HEADER_FMT = "{}.grpc.pb.h"
  27. _GRPC_PROTO_SRC_FMT = "{}.grpc.pb.cc"
  28. _GRPC_PROTO_MOCK_HEADER_FMT = "{}_mock.grpc.pb.h"
  29. _PROTO_HEADER_FMT = "{}.pb.h"
  30. _PROTO_SRC_FMT = "{}.pb.cc"
  31. def _strip_package_from_path(label_package, file):
  32. prefix_len = 0
  33. if not file.is_source and file.path.startswith(file.root.path):
  34. prefix_len = len(file.root.path) + 1
  35. path = file.path
  36. if len(label_package) == 0:
  37. return path
  38. if not path.startswith(label_package + "/", prefix_len):
  39. fail("'{}' does not lie within '{}'.".format(path, label_package))
  40. return path[prefix_len + len(label_package + "/"):]
  41. def _get_srcs_file_path(file):
  42. if not file.is_source and file.path.startswith(file.root.path):
  43. return file.path[len(file.root.path) + 1:]
  44. return file.path
  45. def _join_directories(directories):
  46. massaged_directories = [directory for directory in directories if len(directory) != 0]
  47. return "/".join(massaged_directories)
  48. def generate_cc_impl(ctx):
  49. """Implementation of the generate_cc rule.
  50. Args:
  51. ctx: The context object.
  52. Returns:
  53. The provider for the generated files.
  54. """
  55. protos = [f for src in ctx.attr.srcs for f in src[ProtoInfo].check_deps_sources.to_list()]
  56. includes = [
  57. f
  58. for src in ctx.attr.srcs
  59. for f in src[ProtoInfo].transitive_imports.to_list()
  60. ]
  61. outs = []
  62. proto_root = get_proto_root(
  63. ctx.label.workspace_root,
  64. )
  65. label_package = _join_directories([ctx.label.workspace_root, ctx.label.package])
  66. if ctx.executable.plugin:
  67. outs += [
  68. proto_path_to_generated_filename(
  69. _strip_package_from_path(label_package, proto),
  70. _GRPC_PROTO_HEADER_FMT,
  71. )
  72. for proto in protos
  73. ]
  74. outs += [
  75. proto_path_to_generated_filename(
  76. _strip_package_from_path(label_package, proto),
  77. _GRPC_PROTO_SRC_FMT,
  78. )
  79. for proto in protos
  80. ]
  81. if ctx.attr.generate_mocks:
  82. outs += [
  83. proto_path_to_generated_filename(
  84. _strip_package_from_path(label_package, proto),
  85. _GRPC_PROTO_MOCK_HEADER_FMT,
  86. )
  87. for proto in protos
  88. ]
  89. else:
  90. outs += [
  91. proto_path_to_generated_filename(
  92. _strip_package_from_path(label_package, proto),
  93. _PROTO_HEADER_FMT,
  94. )
  95. for proto in protos
  96. ]
  97. outs += [
  98. proto_path_to_generated_filename(
  99. _strip_package_from_path(label_package, proto),
  100. _PROTO_SRC_FMT,
  101. )
  102. for proto in protos
  103. ]
  104. out_files = [ctx.actions.declare_file(out) for out in outs]
  105. dir_out = str(ctx.genfiles_dir.path + proto_root)
  106. arguments = []
  107. if ctx.executable.plugin:
  108. arguments += get_plugin_args(
  109. ctx.executable.plugin,
  110. ctx.attr.flags,
  111. dir_out,
  112. ctx.attr.generate_mocks,
  113. )
  114. tools = [ctx.executable.plugin]
  115. else:
  116. arguments.append("--cpp_out=" + ",".join(ctx.attr.flags) + ":" + dir_out)
  117. tools = []
  118. arguments += [
  119. "--proto_path={}".format(get_include_directory(i))
  120. for i in includes
  121. ]
  122. # Include the output directory so that protoc puts the generated code in the
  123. # right directory.
  124. arguments.append("--proto_path={0}{1}".format(dir_out, proto_root))
  125. arguments += [_get_srcs_file_path(proto) for proto in protos]
  126. # create a list of well known proto files if the argument is non-None
  127. well_known_proto_files = []
  128. if ctx.attr.well_known_protos:
  129. f = ctx.attr.well_known_protos.files.to_list()[0].dirname
  130. if f != "external/com_google_protobuf/src/google/protobuf":
  131. print(
  132. "Error: Only @com_google_protobuf//:well_known_protos is supported",
  133. ) # buildifier: disable=print
  134. else:
  135. # f points to "external/com_google_protobuf/src/google/protobuf"
  136. # add -I argument to protoc so it knows where to look for the proto files.
  137. arguments.append("-I{0}".format(f + "/../.."))
  138. well_known_proto_files = [
  139. f
  140. for f in ctx.attr.well_known_protos.files.to_list()
  141. ]
  142. ctx.actions.run(
  143. inputs = protos + includes + well_known_proto_files,
  144. tools = tools,
  145. outputs = out_files,
  146. executable = ctx.executable._protoc,
  147. arguments = arguments,
  148. use_default_shell_env = True,
  149. )
  150. return struct(files = depset(out_files)) # buildifier: disable=rule-impl-return
  151. _generate_cc = rule(
  152. attrs = {
  153. "srcs": attr.label_list(
  154. mandatory = True,
  155. allow_empty = False,
  156. providers = [ProtoInfo],
  157. ),
  158. "plugin": attr.label(
  159. executable = True,
  160. providers = ["files_to_run"],
  161. cfg = "host",
  162. ),
  163. "flags": attr.string_list(
  164. mandatory = False,
  165. allow_empty = True,
  166. ),
  167. "well_known_protos": attr.label(mandatory = False),
  168. "generate_mocks": attr.bool(
  169. default = False,
  170. mandatory = False,
  171. ),
  172. "_protoc": attr.label(
  173. default = Label("//external:protocol_compiler"),
  174. executable = True,
  175. cfg = "host",
  176. ),
  177. },
  178. # We generate .h files, so we need to output to genfiles.
  179. output_to_genfiles = True,
  180. implementation = generate_cc_impl,
  181. )
  182. def generate_cc(well_known_protos, **kwargs):
  183. if well_known_protos:
  184. _generate_cc(
  185. well_known_protos = "@com_google_protobuf//:well_known_protos",
  186. **kwargs
  187. )
  188. else:
  189. _generate_cc(**kwargs)