1load("@bazel_skylib//lib:versions.bzl", "versions") 2load("@rules_cc//cc:defs.bzl", "cc_library") 3load("@rules_proto//proto:defs.bzl", "ProtoInfo") 4load("@rules_python//python:defs.bzl", "py_library", "py_test") 5 6def _GetPath(ctx, path): 7 if ctx.label.workspace_root: 8 return ctx.label.workspace_root + "/" + path 9 else: 10 return path 11 12def _IsNewExternal(ctx): 13 # Bazel 0.4.4 and older have genfiles paths that look like: 14 # bazel-out/local-fastbuild/genfiles/external/repo/foo 15 # After the exec root rearrangement, they look like: 16 # ../repo/bazel-out/local-fastbuild/genfiles/foo 17 return ctx.label.workspace_root.startswith("../") 18 19def _GenDir(ctx): 20 if _IsNewExternal(ctx): 21 # We are using the fact that Bazel 0.4.4+ provides repository-relative paths 22 # for ctx.genfiles_dir. 23 return ctx.genfiles_dir.path + ( 24 "/" + ctx.attr.includes[0] if ctx.attr.includes and ctx.attr.includes[0] else "" 25 ) 26 27 # This means that we're either in the old version OR the new version in the local repo. 28 # Either way, appending the source path to the genfiles dir works. 29 return ctx.var["GENDIR"] + "/" + _SourceDir(ctx) 30 31def _SourceDir(ctx): 32 if not ctx.attr.includes: 33 return ctx.label.workspace_root 34 if not ctx.attr.includes[0]: 35 return _GetPath(ctx, ctx.label.package) 36 if not ctx.label.package: 37 return _GetPath(ctx, ctx.attr.includes[0]) 38 return _GetPath(ctx, ctx.label.package + "/" + ctx.attr.includes[0]) 39 40def _CcHdrs(srcs, use_grpc_plugin = False): 41 ret = [s[:-len(".proto")] + ".pb.h" for s in srcs] 42 if use_grpc_plugin: 43 ret += [s[:-len(".proto")] + ".grpc.pb.h" for s in srcs] 44 return ret 45 46def _CcSrcs(srcs, use_grpc_plugin = False): 47 ret = [s[:-len(".proto")] + ".pb.cc" for s in srcs] 48 if use_grpc_plugin: 49 ret += [s[:-len(".proto")] + ".grpc.pb.cc" for s in srcs] 50 return ret 51 52def _CcOuts(srcs, use_grpc_plugin = False): 53 return _CcHdrs(srcs, use_grpc_plugin) + _CcSrcs(srcs, use_grpc_plugin) 54 55def _PyOuts(srcs, use_grpc_plugin = False): 56 ret = [s[:-len(".proto")] + "_pb2.py" for s in srcs] 57 if use_grpc_plugin: 58 ret += [s[:-len(".proto")] + "_pb2_grpc.py" for s in srcs] 59 return ret 60 61def _RelativeOutputPath(path, include, dest = ""): 62 if include == None: 63 return path 64 65 if not path.startswith(include): 66 fail("Include path %s isn't part of the path %s." % (include, path)) 67 68 if include and include[-1] != "/": 69 include = include + "/" 70 if dest and dest[-1] != "/": 71 dest = dest + "/" 72 73 path = path[len(include):] 74 return dest + path 75 76def _proto_gen_impl(ctx): 77 """General implementation for generating protos""" 78 srcs = ctx.files.srcs 79 deps = depset(direct=ctx.files.srcs) 80 source_dir = _SourceDir(ctx) 81 gen_dir = _GenDir(ctx).rstrip("/") 82 import_flags = [] 83 84 if source_dir: 85 has_sources = any([src.is_source for src in srcs]) 86 if has_sources: 87 import_flags += ["-I" + source_dir] 88 else: 89 import_flags += ["-I."] 90 91 has_generated = any([not src.is_source for src in srcs]) 92 if has_generated: 93 import_flags += ["-I" + gen_dir] 94 95 import_flags = depset(direct=import_flags) 96 97 for dep in ctx.attr.deps: 98 if type(dep.proto.import_flags) == "list": 99 import_flags = depset(transitive=[import_flags], direct=dep.proto.import_flags) 100 else: 101 import_flags = depset(transitive=[import_flags, dep.proto.import_flags]) 102 if type(dep.proto.deps) == "list": 103 deps = depset(transitive=[deps], direct=dep.proto.deps) 104 else: 105 deps = depset(transitive=[deps, dep.proto.deps]) 106 107 if not ctx.attr.gen_cc and not ctx.attr.gen_py and not ctx.executable.plugin: 108 return struct( 109 proto = struct( 110 srcs = srcs, 111 import_flags = import_flags, 112 deps = deps, 113 ), 114 ) 115 116 for src in srcs: 117 args = [] 118 119 in_gen_dir = src.root.path == gen_dir 120 if in_gen_dir: 121 import_flags_real = [] 122 for f in import_flags.to_list(): 123 path = f.replace("-I", "") 124 import_flags_real.append("-I$(realpath -s %s)" % path) 125 126 outs = [] 127 use_grpc_plugin = (ctx.attr.plugin_language == "grpc" and ctx.attr.plugin) 128 path_tpl = "$(realpath %s)" if in_gen_dir else "%s" 129 if ctx.attr.gen_cc: 130 args += [("--cpp_out=" + path_tpl) % gen_dir] 131 outs.extend(_CcOuts([src.basename], use_grpc_plugin = use_grpc_plugin)) 132 if ctx.attr.gen_py: 133 args += [("--python_out=" + path_tpl) % gen_dir] 134 outs.extend(_PyOuts([src.basename], use_grpc_plugin = use_grpc_plugin)) 135 136 outs = [ctx.actions.declare_file(out, sibling = src) for out in outs] 137 inputs = [src] + deps.to_list() 138 tools = [ctx.executable.protoc] 139 if ctx.executable.plugin: 140 plugin = ctx.executable.plugin 141 lang = ctx.attr.plugin_language 142 if not lang and plugin.basename.startswith("protoc-gen-"): 143 lang = plugin.basename[len("protoc-gen-"):] 144 if not lang: 145 fail("cannot infer the target language of plugin", "plugin_language") 146 147 outdir = "." if in_gen_dir else gen_dir 148 149 if ctx.attr.plugin_options: 150 outdir = ",".join(ctx.attr.plugin_options) + ":" + outdir 151 args += [("--plugin=protoc-gen-%s=" + path_tpl) % (lang, plugin.path)] 152 args += ["--%s_out=%s" % (lang, outdir)] 153 tools.append(plugin) 154 155 if not in_gen_dir: 156 ctx.actions.run( 157 inputs = inputs, 158 tools = tools, 159 outputs = outs, 160 arguments = args + import_flags.to_list() + [src.path], 161 executable = ctx.executable.protoc, 162 mnemonic = "ProtoCompile", 163 use_default_shell_env = True, 164 ) 165 else: 166 for out in outs: 167 orig_command = " ".join( 168 ["$(realpath %s)" % ctx.executable.protoc.path] + args + 169 import_flags_real + [src.basename], 170 ) 171 command = ";".join([ 172 'CMD="%s"' % orig_command, 173 "cd %s" % src.dirname, 174 "${CMD}", 175 "cd -", 176 ]) 177 generated_out = "/".join([gen_dir, out.basename]) 178 if generated_out != out.path: 179 command += ";mv %s %s" % (generated_out, out.path) 180 ctx.actions.run_shell( 181 inputs = inputs, 182 outputs = [out], 183 command = command, 184 mnemonic = "ProtoCompile", 185 tools = tools, 186 use_default_shell_env = True, 187 ) 188 189 return struct( 190 proto = struct( 191 srcs = srcs, 192 import_flags = import_flags, 193 deps = deps, 194 ), 195 ) 196 197proto_gen = rule( 198 attrs = { 199 "srcs": attr.label_list(allow_files = True), 200 "deps": attr.label_list(providers = ["proto"]), 201 "includes": attr.string_list(), 202 "protoc": attr.label( 203 cfg = "exec", 204 executable = True, 205 allow_single_file = True, 206 mandatory = True, 207 ), 208 "plugin": attr.label( 209 cfg = "exec", 210 allow_files = True, 211 executable = True, 212 ), 213 "plugin_language": attr.string(), 214 "plugin_options": attr.string_list(), 215 "gen_cc": attr.bool(), 216 "gen_py": attr.bool(), 217 "outs": attr.output_list(), 218 }, 219 output_to_genfiles = True, 220 implementation = _proto_gen_impl, 221) 222"""Generates codes from Protocol Buffers definitions. 223 224This rule helps you to implement Skylark macros specific to the target 225language. You should prefer more specific `cc_proto_library `, 226`py_proto_library` and others unless you are adding such wrapper macros. 227 228Args: 229 srcs: Protocol Buffers definition files (.proto) to run the protocol compiler 230 against. 231 deps: a list of dependency labels; must be other proto libraries. 232 includes: a list of include paths to .proto files. 233 protoc: the label of the protocol compiler to generate the sources. 234 plugin: the label of the protocol compiler plugin to be passed to the protocol 235 compiler. 236 plugin_language: the language of the generated sources 237 plugin_options: a list of options to be passed to the plugin 238 gen_cc: generates C++ sources in addition to the ones from the plugin. 239 gen_py: generates Python sources in addition to the ones from the plugin. 240 outs: a list of labels of the expected outputs from the protocol compiler. 241""" 242 243def _adapt_proto_library_impl(ctx): 244 deps = [dep[ProtoInfo] for dep in ctx.attr.deps] 245 246 srcs = [src for dep in deps for src in dep.direct_sources] 247 return struct( 248 proto = struct( 249 srcs = srcs, 250 import_flags = ["-I{}".format(path) for dep in deps for path in dep.transitive_proto_path.to_list()], 251 deps = srcs, 252 ), 253 ) 254 255adapt_proto_library = rule( 256 implementation = _adapt_proto_library_impl, 257 attrs = { 258 "deps": attr.label_list( 259 mandatory = True, 260 providers = [ProtoInfo], 261 ), 262 }, 263 doc = "Adapts `proto_library` from `@rules_proto` to be used with `{cc,py}_proto_library` from this file.", 264) 265 266def cc_proto_library( 267 name, 268 srcs = [], 269 deps = [], 270 cc_libs = [], 271 include = None, 272 protoc = "@com_google_protobuf//:protoc", 273 use_grpc_plugin = False, 274 default_runtime = "@com_google_protobuf//:protobuf", 275 **kargs): 276 """Bazel rule to create a C++ protobuf library from proto source files 277 278 NOTE: the rule is only an internal workaround to generate protos. The 279 interface may change and the rule may be removed when bazel has introduced 280 the native rule. 281 282 Args: 283 name: the name of the cc_proto_library. 284 srcs: the .proto files of the cc_proto_library. 285 deps: a list of dependency labels; must be cc_proto_library. 286 cc_libs: a list of other cc_library targets depended by the generated 287 cc_library. 288 include: a string indicating the include path of the .proto files. 289 protoc: the label of the protocol compiler to generate the sources. 290 use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin 291 when processing the proto files. 292 default_runtime: the implicitly default runtime which will be depended on by 293 the generated cc_library target. 294 **kargs: other keyword arguments that are passed to cc_library. 295 """ 296 297 includes = [] 298 if include != None: 299 includes = [include] 300 301 grpc_cpp_plugin = None 302 if use_grpc_plugin: 303 grpc_cpp_plugin = "//external:grpc_cpp_plugin" 304 305 gen_srcs = _CcSrcs(srcs, use_grpc_plugin) 306 gen_hdrs = _CcHdrs(srcs, use_grpc_plugin) 307 outs = gen_srcs + gen_hdrs 308 309 proto_gen( 310 name = name + "_genproto", 311 srcs = srcs, 312 deps = [s + "_genproto" for s in deps], 313 includes = includes, 314 protoc = protoc, 315 plugin = grpc_cpp_plugin, 316 plugin_language = "grpc", 317 gen_cc = 1, 318 outs = outs, 319 visibility = ["//visibility:public"], 320 ) 321 322 if default_runtime and not default_runtime in cc_libs: 323 cc_libs = cc_libs + [default_runtime] 324 if use_grpc_plugin: 325 cc_libs = cc_libs + ["//external:grpc_lib"] 326 cc_library( 327 name = name, 328 srcs = gen_srcs, 329 hdrs = gen_hdrs, 330 deps = cc_libs + deps, 331 includes = includes, 332 **kargs 333 ) 334 335def _internal_gen_well_known_protos_java_impl(ctx): 336 args = ctx.actions.args() 337 338 deps = [d[ProtoInfo] for d in ctx.attr.deps] 339 340 srcjar = ctx.actions.declare_file("{}.srcjar".format(ctx.attr.name)) 341 if ctx.attr.javalite: 342 java_out = "lite:%s" % srcjar.path 343 else: 344 java_out = srcjar 345 346 args.add("--java_out", java_out) 347 348 descriptors = depset( 349 transitive = [dep.transitive_descriptor_sets for dep in deps], 350 ) 351 args.add_joined( 352 "--descriptor_set_in", 353 descriptors, 354 join_with = ctx.configuration.host_path_separator, 355 ) 356 357 for dep in deps: 358 if "." == dep.proto_source_root: 359 args.add_all([src.path for src in dep.direct_sources]) 360 else: 361 source_root = dep.proto_source_root 362 offset = len(source_root) + 1 # + '/'. 363 args.add_all([src.path[offset:] for src in dep.direct_sources]) 364 365 ctx.actions.run( 366 executable = ctx.executable._protoc, 367 inputs = descriptors, 368 outputs = [srcjar], 369 arguments = [args], 370 use_default_shell_env = True, 371 ) 372 373 return [ 374 DefaultInfo( 375 files = depset([srcjar]), 376 ), 377 ] 378 379internal_gen_well_known_protos_java = rule( 380 implementation = _internal_gen_well_known_protos_java_impl, 381 attrs = { 382 "deps": attr.label_list( 383 mandatory = True, 384 providers = [ProtoInfo], 385 ), 386 "javalite": attr.bool( 387 default = False, 388 ), 389 "_protoc": attr.label( 390 executable = True, 391 cfg = "exec", 392 default = "@com_google_protobuf//:protoc", 393 ), 394 }, 395) 396 397def _internal_gen_kt_protos(ctx): 398 args = ctx.actions.args() 399 400 deps = [d[ProtoInfo] for d in ctx.attr.deps] 401 402 srcjar = ctx.actions.declare_file("{}.srcjar".format(ctx.attr.name)) 403 if ctx.attr.lite: 404 out = "lite:%s" % srcjar.path 405 else: 406 out = srcjar 407 408 args.add("--kotlin_out", out) 409 410 descriptors = depset( 411 transitive = [dep.transitive_descriptor_sets for dep in deps], 412 ) 413 args.add_joined( 414 "--descriptor_set_in", 415 descriptors, 416 join_with = ctx.configuration.host_path_separator, 417 ) 418 419 for dep in deps: 420 if "." == dep.proto_source_root: 421 args.add_all([src.path for src in dep.direct_sources]) 422 else: 423 source_root = dep.proto_source_root 424 offset = len(source_root) + 1 # + '/'. 425 args.add_all([src.path[offset:] for src in dep.direct_sources]) 426 427 ctx.actions.run( 428 executable = ctx.executable._protoc, 429 inputs = descriptors, 430 outputs = [srcjar], 431 arguments = [args], 432 use_default_shell_env = True, 433 ) 434 435 return [ 436 DefaultInfo( 437 files = depset([srcjar]), 438 ), 439 ] 440 441internal_gen_kt_protos = rule( 442 implementation = _internal_gen_kt_protos, 443 attrs = { 444 "deps": attr.label_list( 445 mandatory = True, 446 providers = [ProtoInfo], 447 ), 448 "lite": attr.bool( 449 default = False, 450 ), 451 "_protoc": attr.label( 452 executable = True, 453 cfg = "exec", 454 default = "//:protoc", 455 ), 456 }, 457) 458 459 460 461def internal_copied_filegroup(name, srcs, strip_prefix, dest, **kwargs): 462 """Macro to copy files to a different directory and then create a filegroup. 463 464 This is used by the //:protobuf_python py_proto_library target to work around 465 an issue caused by Python source files that are part of the same Python 466 package being in separate directories. 467 468 Args: 469 srcs: The source files to copy and add to the filegroup. 470 strip_prefix: Path to the root of the files to copy. 471 dest: The directory to copy the source files into. 472 **kwargs: extra arguments that will be passesd to the filegroup. 473 """ 474 outs = [_RelativeOutputPath(s, strip_prefix, dest) for s in srcs] 475 476 native.genrule( 477 name = name + "_genrule", 478 srcs = srcs, 479 outs = outs, 480 cmd_bash = " && ".join( 481 ["cp $(location %s) $(location %s)" % 482 (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs]), 483 cmd_bat = " && ".join( 484 ["@copy /Y $(location %s) $(location %s) >NUL" % 485 (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs]), 486 ) 487 488 native.filegroup( 489 name = name, 490 srcs = outs, 491 **kwargs 492 ) 493 494def py_proto_library( 495 name, 496 srcs = [], 497 deps = [], 498 py_libs = [], 499 py_extra_srcs = [], 500 include = None, 501 default_runtime = "@com_google_protobuf//:protobuf_python", 502 protoc = "@com_google_protobuf//:protoc", 503 use_grpc_plugin = False, 504 **kargs): 505 """Bazel rule to create a Python protobuf library from proto source files 506 507 NOTE: the rule is only an internal workaround to generate protos. The 508 interface may change and the rule may be removed when bazel has introduced 509 the native rule. 510 511 Args: 512 name: the name of the py_proto_library. 513 srcs: the .proto files of the py_proto_library. 514 deps: a list of dependency labels; must be py_proto_library. 515 py_libs: a list of other py_library targets depended by the generated 516 py_library. 517 py_extra_srcs: extra source files that will be added to the output 518 py_library. This attribute is used for internal bootstrapping. 519 include: a string indicating the include path of the .proto files. 520 default_runtime: the implicitly default runtime which will be depended on by 521 the generated py_library target. 522 protoc: the label of the protocol compiler to generate the sources. 523 use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin 524 when processing the proto files. 525 **kargs: other keyword arguments that are passed to py_library. 526 527 """ 528 outs = _PyOuts(srcs, use_grpc_plugin) 529 530 includes = [] 531 if include != None: 532 includes = [include] 533 534 grpc_python_plugin = None 535 if use_grpc_plugin: 536 grpc_python_plugin = "//external:grpc_python_plugin" 537 # Note: Generated grpc code depends on Python grpc module. This dependency 538 # is not explicitly listed in py_libs. Instead, host system is assumed to 539 # have grpc installed. 540 541 proto_gen( 542 name = name + "_genproto", 543 srcs = srcs, 544 deps = [s + "_genproto" for s in deps], 545 includes = includes, 546 protoc = protoc, 547 gen_py = 1, 548 outs = outs, 549 visibility = ["//visibility:public"], 550 plugin = grpc_python_plugin, 551 plugin_language = "grpc", 552 ) 553 554 if default_runtime and not default_runtime in py_libs + deps: 555 py_libs = py_libs + [default_runtime] 556 py_library( 557 name = name, 558 srcs = outs + py_extra_srcs, 559 deps = py_libs + deps, 560 imports = includes, 561 **kargs 562 ) 563 564def internal_protobuf_py_tests( 565 name, 566 modules = [], 567 **kargs): 568 """Bazel rules to create batch tests for protobuf internal. 569 570 Args: 571 name: the name of the rule. 572 modules: a list of modules for tests. The macro will create a py_test for 573 each of the parameter with the source "google/protobuf/%s.py" 574 kargs: extra parameters that will be passed into the py_test. 575 576 """ 577 for m in modules: 578 s = "python/google/protobuf/internal/%s.py" % m 579 py_test( 580 name = "py_%s" % m, 581 srcs = [s], 582 main = s, 583 **kargs 584 ) 585 586def check_protobuf_required_bazel_version(): 587 """For WORKSPACE files, to check the installed version of bazel. 588 589 This ensures bazel supports our approach to proto_library() depending on a 590 copied filegroup. (Fixed in bazel 0.5.4) 591 """ 592 versions.check(minimum_bazel_version = "0.5.4") 593