1# Copyright (c) 2009-2021, Google LLC 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# * Redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer. 8# * Redistributions in binary form must reproduce the above copyright 9# notice, this list of conditions and the following disclaimer in the 10# documentation and/or other materials provided with the distribution. 11# * Neither the name of Google LLC nor the 12# names of its contributors may be used to endorse or promote products 13# derived from this software without specific prior written permission. 14# 15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18# DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY 19# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 26"""Public rules for using upb protos: 27 - upb_proto_library() 28 - upb_proto_reflection_library() 29""" 30 31load("@bazel_skylib//lib:paths.bzl", "paths") 32 33# begin:google_only 34# load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain") 35# end:google_only 36 37# begin:github_only 38# Compatibility code for Bazel 4.x. Remove this when we drop support for Bazel 4.x. 39load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") 40 41def use_cpp_toolchain(): 42 return ["@bazel_tools//tools/cpp:toolchain_type"] 43# end:github_only 44 45# Generic support code ######################################################### 46 47# begin:github_only 48_is_google3 = False 49# end:github_only 50 51# begin:google_only 52# _is_google3 = True 53# end:google_only 54 55def _get_real_short_path(file): 56 # For some reason, files from other archives have short paths that look like: 57 # ../com_google_protobuf/google/protobuf/descriptor.proto 58 short_path = file.short_path 59 if short_path.startswith("../"): 60 second_slash = short_path.index("/", 3) 61 short_path = short_path[second_slash + 1:] 62 63 # Sometimes it has another few prefixes like: 64 # _virtual_imports/any_proto/google/protobuf/any.proto 65 # benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto 66 # We want just google/protobuf/any.proto. 67 virtual_imports = "_virtual_imports/" 68 if virtual_imports in short_path: 69 short_path = short_path.split(virtual_imports)[1].split("/", 1)[1] 70 return short_path 71 72def _get_real_root(ctx, file): 73 real_short_path = _get_real_short_path(file) 74 root = file.path[:-len(real_short_path) - 1] 75 76 if not _is_google3 and ctx.rule.attr.strip_import_prefix: 77 root = paths.join(root, ctx.rule.attr.strip_import_prefix[1:]) 78 return root 79 80def _generate_output_file(ctx, src, extension): 81 package = ctx.label.package 82 if not _is_google3: 83 strip_import_prefix = ctx.rule.attr.strip_import_prefix 84 if strip_import_prefix and strip_import_prefix != "/": 85 if not package.startswith(strip_import_prefix[1:]): 86 fail("%s does not begin with prefix %s" % (package, strip_import_prefix)) 87 package = package[len(strip_import_prefix):] 88 89 real_short_path = _get_real_short_path(src) 90 real_short_path = paths.relativize(real_short_path, package) 91 output_filename = paths.replace_extension(real_short_path, extension) 92 ret = ctx.actions.declare_file(output_filename) 93 return ret 94 95def _generate_include_path(src, out, extension): 96 short_path = _get_real_short_path(src) 97 short_path = paths.replace_extension(short_path, extension) 98 if not out.path.endswith(short_path): 99 fail("%s does not end with %s" % (out.path, short_path)) 100 101 return out.path[:-len(short_path)] 102 103def _filter_none(elems): 104 out = [] 105 for elem in elems: 106 if elem: 107 out.append(elem) 108 return out 109 110def _cc_library_func(ctx, name, hdrs, srcs, copts, includes, dep_ccinfos): 111 """Like cc_library(), but callable from rules. 112 113 Args: 114 ctx: Rule context. 115 name: Unique name used to generate output files. 116 hdrs: Public headers that can be #included from other rules. 117 srcs: C/C++ source files. 118 copts: Additional options for cc compilation. 119 includes: Additional include paths. 120 dep_ccinfos: CcInfo providers of dependencies we should build/link against. 121 122 Returns: 123 CcInfo provider for this compilation. 124 """ 125 126 compilation_contexts = [info.compilation_context for info in dep_ccinfos] 127 linking_contexts = [info.linking_context for info in dep_ccinfos] 128 toolchain = find_cpp_toolchain(ctx) 129 feature_configuration = cc_common.configure_features( 130 ctx = ctx, 131 cc_toolchain = toolchain, 132 requested_features = ctx.features, 133 unsupported_features = ctx.disabled_features, 134 ) 135 136 blaze_only_args = {} 137 138 if _is_google3: 139 blaze_only_args["grep_includes"] = ctx.file._grep_includes 140 141 (compilation_context, compilation_outputs) = cc_common.compile( 142 actions = ctx.actions, 143 feature_configuration = feature_configuration, 144 cc_toolchain = toolchain, 145 name = name, 146 srcs = srcs, 147 includes = includes, 148 public_hdrs = hdrs, 149 user_compile_flags = copts, 150 compilation_contexts = compilation_contexts, 151 **blaze_only_args 152 ) 153 154 # buildifier: disable=unused-variable 155 (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs( 156 actions = ctx.actions, 157 name = name, 158 feature_configuration = feature_configuration, 159 cc_toolchain = toolchain, 160 compilation_outputs = compilation_outputs, 161 linking_contexts = linking_contexts, 162 disallow_dynamic_library = cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "targets_windows"), 163 **blaze_only_args 164 ) 165 166 return CcInfo( 167 compilation_context = compilation_context, 168 linking_context = linking_context, 169 ) 170 171# Dummy rule to expose select() copts to aspects ############################## 172 173UpbProtoLibraryCoptsInfo = provider( 174 "Provides copts for upb proto targets", 175 fields = { 176 "copts": "copts for upb_proto_library()", 177 }, 178) 179 180def upb_proto_library_copts_impl(ctx): 181 return UpbProtoLibraryCoptsInfo(copts = ctx.attr.copts) 182 183upb_proto_library_copts = rule( 184 implementation = upb_proto_library_copts_impl, 185 attrs = {"copts": attr.string_list(default = [])}, 186) 187 188# upb_proto_library / upb_proto_reflection_library shared code ################# 189 190GeneratedSrcsInfo = provider( 191 "Provides generated headers and sources", 192 fields = { 193 "srcs": "list of srcs", 194 "hdrs": "list of hdrs", 195 "thunks": "Experimental, do not use. List of srcs defining C API. Incompatible with hdrs.", 196 "includes": "list of extra includes", 197 }, 198) 199 200UpbWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info", "cc_info_with_thunks"]) 201_UpbDefsWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info"]) 202_UpbWrappedGeneratedSrcsInfo = provider("Provider for generated sources", fields = ["srcs"]) 203_WrappedDefsGeneratedSrcsInfo = provider( 204 "Provider for generated reflective sources", 205 fields = ["srcs"], 206) 207 208def _compile_upb_protos(ctx, generator, proto_info, proto_sources): 209 if len(proto_sources) == 0: 210 return GeneratedSrcsInfo(srcs = [], hdrs = [], thunks = [], includes = []) 211 212 ext = "." + generator 213 tool = getattr(ctx.executable, "_gen_" + generator) 214 srcs = [_generate_output_file(ctx, name, ext + ".c") for name in proto_sources] 215 hdrs = [_generate_output_file(ctx, name, ext + ".h") for name in proto_sources] 216 thunks = [] 217 if generator == "upb": 218 thunks = [_generate_output_file(ctx, name, ext + ".thunks.c") for name in proto_sources] 219 transitive_sets = proto_info.transitive_descriptor_sets.to_list() 220 221 args = ctx.actions.args() 222 args.use_param_file(param_file_arg = "@%s") 223 args.set_param_file_format("multiline") 224 225 args.add("--" + generator + "_out=" + _get_real_root(ctx, srcs[0])) 226 args.add("--plugin=protoc-gen-" + generator + "=" + tool.path) 227 args.add("--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets])) 228 args.add_all(proto_sources, map_each = _get_real_short_path) 229 230 ctx.actions.run( 231 inputs = depset( 232 direct = [proto_info.direct_descriptor_set], 233 transitive = [proto_info.transitive_descriptor_sets], 234 ), 235 tools = [tool], 236 outputs = srcs + hdrs, 237 executable = ctx.executable._protoc, 238 arguments = [args], 239 progress_message = "Generating upb protos for :" + ctx.label.name, 240 mnemonic = "GenUpbProtos", 241 ) 242 if generator == "upb": 243 ctx.actions.run_shell( 244 inputs = hdrs, 245 outputs = thunks, 246 command = " && ".join([ 247 "sed 's/UPB_INLINE //' {} > {}".format(hdr.path, thunk.path) 248 for (hdr, thunk) in zip(hdrs, thunks) 249 ]), 250 progress_message = "Generating thunks for upb protos API for: " + ctx.label.name, 251 mnemonic = "GenUpbProtosThunks", 252 ) 253 return GeneratedSrcsInfo( 254 srcs = srcs, 255 hdrs = hdrs, 256 thunks = thunks, 257 includes = [_generate_include_path(proto_sources[0], hdrs[0], ext + ".h")], 258 ) 259 260def _upb_proto_rule_impl(ctx): 261 if len(ctx.attr.deps) != 1: 262 fail("only one deps dependency allowed.") 263 dep = ctx.attr.deps[0] 264 265 if _WrappedDefsGeneratedSrcsInfo in dep: 266 srcs = dep[_WrappedDefsGeneratedSrcsInfo].srcs 267 elif _UpbWrappedGeneratedSrcsInfo in dep: 268 srcs = dep[_UpbWrappedGeneratedSrcsInfo].srcs 269 else: 270 fail("proto_library rule must generate _UpbWrappedGeneratedSrcsInfo or " + 271 "_WrappedDefsGeneratedSrcsInfo (aspect should have handled this).") 272 273 if _UpbDefsWrappedCcInfo in dep: 274 cc_info = dep[_UpbDefsWrappedCcInfo].cc_info 275 elif UpbWrappedCcInfo in dep: 276 cc_info = dep[UpbWrappedCcInfo].cc_info 277 else: 278 fail("proto_library rule must generate UpbWrappedCcInfo or " + 279 "_UpbDefsWrappedCcInfo (aspect should have handled this).") 280 281 lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0] 282 files = _filter_none([ 283 lib.static_library, 284 lib.pic_static_library, 285 lib.dynamic_library, 286 ]) 287 return [ 288 DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)), 289 srcs, 290 cc_info, 291 ] 292 293def _upb_proto_aspect_impl(target, ctx, generator, cc_provider, file_provider): 294 proto_info = target[ProtoInfo] 295 files = _compile_upb_protos(ctx, generator, proto_info, proto_info.direct_sources) 296 deps = ctx.rule.attr.deps + getattr(ctx.attr, "_" + generator) 297 dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep] 298 dep_ccinfos += [dep[UpbWrappedCcInfo].cc_info for dep in deps if UpbWrappedCcInfo in dep] 299 dep_ccinfos += [dep[_UpbDefsWrappedCcInfo].cc_info for dep in deps if _UpbDefsWrappedCcInfo in dep] 300 if generator == "upbdefs": 301 if UpbWrappedCcInfo not in target: 302 fail("Target should have UpbWrappedCcInfo provider") 303 dep_ccinfos.append(target[UpbWrappedCcInfo].cc_info) 304 cc_info = _cc_library_func( 305 ctx = ctx, 306 name = ctx.rule.attr.name + "." + generator, 307 hdrs = files.hdrs, 308 srcs = files.srcs, 309 includes = files.includes, 310 copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts, 311 dep_ccinfos = dep_ccinfos, 312 ) 313 314 if files.thunks: 315 cc_info_with_thunks = _cc_library_func( 316 ctx = ctx, 317 name = ctx.rule.attr.name + "." + generator + ".thunks", 318 hdrs = [], 319 srcs = files.thunks, 320 includes = files.includes, 321 copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts, 322 dep_ccinfos = dep_ccinfos + [cc_info], 323 ) 324 wrapped_cc_info = cc_provider( 325 cc_info = cc_info, 326 cc_info_with_thunks = cc_info_with_thunks, 327 ) 328 else: 329 wrapped_cc_info = cc_provider( 330 cc_info = cc_info, 331 ) 332 return [ 333 wrapped_cc_info, 334 file_provider(srcs = files), 335 ] 336 337def upb_proto_library_aspect_impl(target, ctx): 338 return _upb_proto_aspect_impl(target, ctx, "upb", UpbWrappedCcInfo, _UpbWrappedGeneratedSrcsInfo) 339 340def _upb_proto_reflection_library_aspect_impl(target, ctx): 341 return _upb_proto_aspect_impl(target, ctx, "upbdefs", _UpbDefsWrappedCcInfo, _WrappedDefsGeneratedSrcsInfo) 342 343def _maybe_add(d): 344 if _is_google3: 345 d["_grep_includes"] = attr.label( 346 allow_single_file = True, 347 cfg = "exec", 348 default = "@bazel_tools//tools/cpp:grep-includes", 349 ) 350 return d 351 352# upb_proto_library() ########################################################## 353 354upb_proto_library_aspect = aspect( 355 attrs = _maybe_add({ 356 "_copts": attr.label( 357 default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use", 358 ), 359 "_gen_upb": attr.label( 360 executable = True, 361 cfg = "exec", 362 default = "//upbc:protoc-gen-upb_stage1", 363 ), 364 "_protoc": attr.label( 365 executable = True, 366 cfg = "exec", 367 default = "@com_google_protobuf//:protoc", 368 ), 369 "_cc_toolchain": attr.label( 370 default = "@bazel_tools//tools/cpp:current_cc_toolchain", 371 ), 372 "_upb": attr.label_list(default = [ 373 "//:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", 374 ]), 375 "_fasttable_enabled": attr.label(default = "//:fasttable_enabled"), 376 }), 377 implementation = upb_proto_library_aspect_impl, 378 provides = [ 379 UpbWrappedCcInfo, 380 _UpbWrappedGeneratedSrcsInfo, 381 ], 382 attr_aspects = ["deps"], 383 fragments = ["cpp"], 384 toolchains = use_cpp_toolchain(), 385 incompatible_use_toolchain_transition = True, 386) 387 388upb_proto_library = rule( 389 output_to_genfiles = True, 390 implementation = _upb_proto_rule_impl, 391 attrs = { 392 "deps": attr.label_list( 393 aspects = [upb_proto_library_aspect], 394 allow_rules = ["proto_library"], 395 providers = [ProtoInfo], 396 ), 397 }, 398) 399 400# upb_proto_reflection_library() ############################################### 401 402_upb_proto_reflection_library_aspect = aspect( 403 attrs = _maybe_add({ 404 "_copts": attr.label( 405 default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use", 406 ), 407 "_gen_upbdefs": attr.label( 408 executable = True, 409 cfg = "exec", 410 default = "//upbc:protoc-gen-upbdefs", 411 ), 412 "_protoc": attr.label( 413 executable = True, 414 cfg = "exec", 415 default = "@com_google_protobuf//:protoc", 416 ), 417 "_cc_toolchain": attr.label( 418 default = "@bazel_tools//tools/cpp:current_cc_toolchain", 419 ), 420 "_upbdefs": attr.label_list( 421 default = [ 422 "//:generated_reflection_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", 423 ], 424 ), 425 }), 426 implementation = _upb_proto_reflection_library_aspect_impl, 427 provides = [ 428 _UpbDefsWrappedCcInfo, 429 _WrappedDefsGeneratedSrcsInfo, 430 ], 431 required_aspect_providers = [ 432 UpbWrappedCcInfo, 433 _UpbWrappedGeneratedSrcsInfo, 434 ], 435 attr_aspects = ["deps"], 436 fragments = ["cpp"], 437 toolchains = use_cpp_toolchain(), 438 incompatible_use_toolchain_transition = True, 439) 440 441upb_proto_reflection_library = rule( 442 output_to_genfiles = True, 443 implementation = _upb_proto_rule_impl, 444 attrs = { 445 "deps": attr.label_list( 446 aspects = [ 447 upb_proto_library_aspect, 448 _upb_proto_reflection_library_aspect, 449 ], 450 allow_rules = ["proto_library"], 451 providers = [ProtoInfo], 452 ), 453 }, 454) 455