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