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 15""" 16This module contains build rules relating to gRPC Objective-C. 17""" 18 19load("@rules_proto//proto:defs.bzl", "ProtoInfo") 20load( 21 "//bazel:protobuf.bzl", 22 "get_include_directory", 23 "get_plugin_args", 24 "proto_path_to_generated_filename", 25) 26load(":grpc_util.bzl", "to_upper_camel_with_extension") 27 28_GRPC_PROTO_HEADER_FMT = "{}.pbrpc.h" 29_GRPC_PROTO_SRC_FMT = "{}.pbrpc.m" 30_PROTO_HEADER_FMT = "{}.pbobjc.h" 31_PROTO_SRC_FMT = "{}.pbobjc.m" 32_GENERATED_PROTOS_DIR = "_generated_protos" 33 34_GENERATE_HDRS = 1 35_GENERATE_SRCS = 2 36_GENERATE_NON_ARC_SRCS = 3 37 38def _generate_objc_impl(ctx): 39 """Implementation of the generate_objc rule.""" 40 protos = [ 41 f 42 for src in ctx.attr.deps 43 for f in src[ProtoInfo].transitive_imports.to_list() 44 ] 45 46 target_package = _join_directories([ctx.label.workspace_root, ctx.label.package]) 47 48 files_with_rpc = [_label_to_full_file_path(f, target_package) for f in ctx.attr.srcs] 49 50 outs = [] 51 for proto in protos: 52 outs.append(_get_output_file_name_from_proto(proto, _PROTO_HEADER_FMT)) 53 outs.append(_get_output_file_name_from_proto(proto, _PROTO_SRC_FMT)) 54 55 file_path = _get_full_path_from_file(proto) 56 if file_path in files_with_rpc: 57 outs.append(_get_output_file_name_from_proto(proto, _GRPC_PROTO_HEADER_FMT)) 58 outs.append(_get_output_file_name_from_proto(proto, _GRPC_PROTO_SRC_FMT)) 59 60 out_files = [ctx.actions.declare_file(out) for out in outs] 61 dir_out = _join_directories([ 62 str(ctx.genfiles_dir.path), 63 target_package, 64 _GENERATED_PROTOS_DIR, 65 ]) 66 67 arguments = [] 68 tools = [] 69 if ctx.executable.plugin: 70 arguments += get_plugin_args( 71 ctx.executable.plugin, 72 [], 73 dir_out, 74 False, 75 ) 76 tools = [ctx.executable.plugin] 77 arguments.append("--objc_out=" + dir_out) 78 79 arguments.append("--proto_path=.") 80 arguments += [ 81 "--proto_path={}".format(get_include_directory(i)) 82 for i in protos 83 ] 84 85 # Include the output directory so that protoc puts the generated code in the 86 # right directory. 87 arguments.append("--proto_path={}".format(dir_out)) 88 arguments += ["--proto_path={}".format(_get_directory_from_proto(proto)) for proto in protos] 89 arguments += [_get_full_path_from_file(proto) for proto in protos] 90 91 # create a list of well known proto files if the argument is non-None 92 well_known_proto_files = [] 93 if ctx.attr.use_well_known_protos: 94 f = ctx.attr.well_known_protos.files.to_list()[0].dirname 95 96 # go two levels up so that #import "google/protobuf/..." is correct 97 arguments.append("-I{0}".format(f + "/../..")) 98 well_known_proto_files = ctx.attr.well_known_protos.files.to_list() 99 ctx.actions.run( 100 inputs = protos + well_known_proto_files, 101 tools = tools, 102 outputs = out_files, 103 executable = ctx.executable._protoc, 104 arguments = arguments, 105 ) 106 107 return struct(files = depset(out_files)) # buildifier: disable=rule-impl-return 108 109def _label_to_full_file_path(src, package): 110 if not src.startswith("//"): 111 # Relative from current package 112 if not src.startswith(":"): 113 # "a.proto" -> ":a.proto" 114 src = ":" + src 115 src = "//" + package + src 116 117 # Converts //path/to/package:File.ext to path/to/package/File.ext. 118 src = src.replace("//", "") 119 src = src.replace(":", "/") 120 if src.startswith("/"): 121 # "//:a.proto" -> "/a.proto" so remove the initial slash 122 return src[1:] 123 else: 124 return src 125 126def _get_output_file_name_from_proto(proto, fmt): 127 return proto_path_to_generated_filename( 128 _GENERATED_PROTOS_DIR + "/" + 129 _get_directory_from_proto(proto) + _get_slash_or_null_from_proto(proto) + 130 to_upper_camel_with_extension(_get_file_name_from_proto(proto), "proto"), 131 fmt, 132 ) 133 134def _get_file_name_from_proto(proto): 135 return proto.path.rpartition("/")[2] 136 137def _get_slash_or_null_from_proto(proto): 138 """Potentially returns empty (if the file is in the root directory)""" 139 return proto.path.rpartition("/")[1] 140 141def _get_directory_from_proto(proto): 142 return proto.path.rpartition("/")[0] 143 144def _get_full_path_from_file(file): 145 gen_dir_length = 0 146 147 # if file is generated, then prepare to remote its root 148 # (including CPU architecture...) 149 if not file.is_source: 150 gen_dir_length = len(file.root.path) + 1 151 152 return file.path[gen_dir_length:] 153 154def _join_directories(directories): 155 massaged_directories = [directory for directory in directories if len(directory) != 0] 156 return "/".join(massaged_directories) 157 158generate_objc = rule( 159 attrs = { 160 "deps": attr.label_list( 161 mandatory = True, 162 allow_empty = False, 163 providers = [ProtoInfo], 164 ), 165 "plugin": attr.label( 166 default = "@com_github_grpc_grpc//src/compiler:grpc_objective_c_plugin", 167 executable = True, 168 providers = ["files_to_run"], 169 cfg = "exec", 170 ), 171 "srcs": attr.string_list( 172 mandatory = False, 173 allow_empty = True, 174 ), 175 "use_well_known_protos": attr.bool( 176 mandatory = False, 177 default = False, 178 ), 179 "well_known_protos": attr.label( 180 default = "@com_google_protobuf//:well_known_type_protos", 181 ), 182 "_protoc": attr.label( 183 default = Label("//external:protocol_compiler"), 184 executable = True, 185 cfg = "exec", 186 ), 187 }, 188 output_to_genfiles = True, 189 implementation = _generate_objc_impl, 190) 191 192def _group_objc_files_impl(ctx): 193 suffix = "" 194 if ctx.attr.gen_mode == _GENERATE_HDRS: 195 suffix = "h" 196 elif ctx.attr.gen_mode == _GENERATE_SRCS: 197 suffix = "pbrpc.m" 198 elif ctx.attr.gen_mode == _GENERATE_NON_ARC_SRCS: 199 suffix = "pbobjc.m" 200 else: 201 fail("Undefined gen_mode") 202 out_files = [ 203 file 204 for file in ctx.attr.src.files.to_list() 205 if file.basename.endswith(suffix) 206 ] 207 return struct(files = depset(out_files)) # buildifier: disable=rule-impl-return 208 209generate_objc_hdrs = rule( 210 attrs = { 211 "src": attr.label( 212 mandatory = True, 213 ), 214 "gen_mode": attr.int( 215 default = _GENERATE_HDRS, 216 ), 217 }, 218 implementation = _group_objc_files_impl, 219) 220 221generate_objc_srcs = rule( 222 attrs = { 223 "src": attr.label( 224 mandatory = True, 225 ), 226 "gen_mode": attr.int( 227 default = _GENERATE_SRCS, 228 ), 229 }, 230 implementation = _group_objc_files_impl, 231) 232 233generate_objc_non_arc_srcs = rule( 234 attrs = { 235 "src": attr.label( 236 mandatory = True, 237 ), 238 "gen_mode": attr.int( 239 default = _GENERATE_NON_ARC_SRCS, 240 ), 241 }, 242 implementation = _group_objc_files_impl, 243) 244