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 16This is an internal rule used by cc_grpc_library, and shouldn't be used 17directly. 18""" 19 20load("@rules_proto//proto:defs.bzl", "ProtoInfo") 21load( 22 "//bazel:protobuf.bzl", 23 "get_include_directory", 24 "get_plugin_args", 25 "get_proto_root", 26 "proto_path_to_generated_filename", 27) 28 29_GRPC_PROTO_HEADER_FMT = "{}.grpc.pb.h" 30_GRPC_PROTO_SRC_FMT = "{}.grpc.pb.cc" 31_GRPC_PROTO_MOCK_HEADER_FMT = "{}_mock.grpc.pb.h" 32_PROTO_HEADER_FMT = "{}.pb.h" 33_PROTO_SRC_FMT = "{}.pb.cc" 34 35def _strip_package_from_path(label_package, file): 36 prefix_len = 0 37 if not file.is_source and file.path.startswith(file.root.path): 38 prefix_len = len(file.root.path) + 1 39 40 path = file.path 41 if len(label_package) == 0: 42 return path 43 if not path.startswith(label_package + "/", prefix_len): 44 fail("'{}' does not lie within '{}'.".format(path, label_package)) 45 return path[prefix_len + len(label_package + "/"):] 46 47def _get_srcs_file_path(file): 48 if not file.is_source and file.path.startswith(file.root.path): 49 return file.path[len(file.root.path) + 1:] 50 return file.path 51 52def _join_directories(directories): 53 massaged_directories = [directory for directory in directories if len(directory) != 0] 54 return "/".join(massaged_directories) 55 56def generate_cc_impl(ctx): 57 """Implementation of the generate_cc rule. 58 59 Args: 60 ctx: The context object. 61 Returns: 62 The provider for the generated files. 63 """ 64 protos = [f for src in ctx.attr.srcs for f in src[ProtoInfo].check_deps_sources.to_list()] 65 includes = [ 66 f 67 for src in ctx.attr.srcs 68 for f in src[ProtoInfo].transitive_imports.to_list() 69 ] 70 outs = [] 71 proto_root = get_proto_root( 72 ctx.label.workspace_root, 73 ) 74 75 label_package = _join_directories([ctx.label.workspace_root, ctx.label.package]) 76 if ctx.executable.plugin: 77 outs += [ 78 proto_path_to_generated_filename( 79 _strip_package_from_path(label_package, proto), 80 _GRPC_PROTO_HEADER_FMT, 81 ) 82 for proto in protos 83 ] 84 outs += [ 85 proto_path_to_generated_filename( 86 _strip_package_from_path(label_package, proto), 87 _GRPC_PROTO_SRC_FMT, 88 ) 89 for proto in protos 90 ] 91 if ctx.attr.generate_mocks: 92 outs += [ 93 proto_path_to_generated_filename( 94 _strip_package_from_path(label_package, proto), 95 _GRPC_PROTO_MOCK_HEADER_FMT, 96 ) 97 for proto in protos 98 ] 99 else: 100 outs += [ 101 proto_path_to_generated_filename( 102 _strip_package_from_path(label_package, proto), 103 _PROTO_HEADER_FMT, 104 ) 105 for proto in protos 106 ] 107 outs += [ 108 proto_path_to_generated_filename( 109 _strip_package_from_path(label_package, proto), 110 _PROTO_SRC_FMT, 111 ) 112 for proto in protos 113 ] 114 out_files = [ctx.actions.declare_file(out) for out in outs] 115 dir_out = str(ctx.genfiles_dir.path + proto_root) 116 117 arguments = [] 118 if ctx.executable.plugin: 119 arguments += get_plugin_args( 120 ctx.executable.plugin, 121 ctx.attr.flags, 122 dir_out, 123 ctx.attr.generate_mocks, 124 ) 125 tools = [ctx.executable.plugin] 126 else: 127 arguments.append("--cpp_out=" + ",".join(ctx.attr.flags) + ":" + dir_out) 128 tools = [] 129 130 arguments += [ 131 "--proto_path={}".format(get_include_directory(i)) 132 for i in includes 133 ] 134 135 # Include the output directory so that protoc puts the generated code in the 136 # right directory. 137 arguments.append("--proto_path={0}{1}".format(dir_out, proto_root)) 138 arguments += [_get_srcs_file_path(proto) for proto in protos] 139 140 # create a list of well known proto files if the argument is non-None 141 well_known_proto_files = [] 142 if ctx.attr.well_known_protos: 143 f = ctx.attr.well_known_protos.files.to_list()[0].dirname 144 if f != "external/com_google_protobuf/src/google/protobuf": 145 print( 146 "Error: Only @com_google_protobuf//:well_known_type_protos is supported", 147 ) # buildifier: disable=print 148 else: 149 # f points to "external/com_google_protobuf/src/google/protobuf" 150 # add -I argument to protoc so it knows where to look for the proto files. 151 arguments.append("-I{0}".format(f + "/../..")) 152 well_known_proto_files = [ 153 f 154 for f in ctx.attr.well_known_protos.files.to_list() 155 ] 156 157 ctx.actions.run( 158 inputs = protos + includes + well_known_proto_files, 159 tools = tools, 160 outputs = out_files, 161 executable = ctx.executable._protoc, 162 arguments = arguments, 163 use_default_shell_env = True, 164 ) 165 166 return DefaultInfo(files = depset(out_files)) 167 168_generate_cc = rule( 169 attrs = { 170 "srcs": attr.label_list( 171 mandatory = True, 172 allow_empty = False, 173 providers = [ProtoInfo], 174 ), 175 "plugin": attr.label( 176 executable = True, 177 providers = ["files_to_run"], 178 cfg = "exec", 179 ), 180 "flags": attr.string_list( 181 mandatory = False, 182 allow_empty = True, 183 ), 184 "well_known_protos": attr.label(mandatory = False), 185 "generate_mocks": attr.bool( 186 default = False, 187 mandatory = False, 188 ), 189 "_protoc": attr.label( 190 default = Label("//external:protocol_compiler"), 191 executable = True, 192 cfg = "exec", 193 ), 194 }, 195 # We generate .h files, so we need to output to genfiles. 196 output_to_genfiles = True, 197 implementation = generate_cc_impl, 198) 199 200def generate_cc(well_known_protos, **kwargs): 201 if well_known_protos: 202 _generate_cc( 203 well_known_protos = "@com_google_protobuf//:well_known_type_protos", 204 **kwargs 205 ) 206 else: 207 _generate_cc(**kwargs) 208