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