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 use_default_shell_env = True, 356 ) 357 358 return [ 359 DefaultInfo( 360 files = depset([srcjar]), 361 ), 362 ] 363 364internal_gen_well_known_protos_java = rule( 365 implementation = _internal_gen_well_known_protos_java_impl, 366 attrs = { 367 "deps": attr.label_list( 368 mandatory = True, 369 providers = [ProtoInfo], 370 ), 371 "_protoc": attr.label( 372 executable = True, 373 cfg = "host", 374 default = "@com_google_protobuf//:protoc", 375 ), 376 }, 377) 378 379def internal_copied_filegroup(name, srcs, strip_prefix, dest, **kwargs): 380 """Macro to copy files to a different directory and then create a filegroup. 381 382 This is used by the //:protobuf_python py_proto_library target to work around 383 an issue caused by Python source files that are part of the same Python 384 package being in separate directories. 385 386 Args: 387 srcs: The source files to copy and add to the filegroup. 388 strip_prefix: Path to the root of the files to copy. 389 dest: The directory to copy the source files into. 390 **kwargs: extra arguments that will be passesd to the filegroup. 391 """ 392 outs = [_RelativeOutputPath(s, strip_prefix, dest) for s in srcs] 393 394 native.genrule( 395 name = name + "_genrule", 396 srcs = srcs, 397 outs = outs, 398 cmd = " && ".join( 399 ["cp $(location %s) $(location %s)" % 400 (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs], 401 ), 402 ) 403 404 native.filegroup( 405 name = name, 406 srcs = outs, 407 **kwargs 408 ) 409 410def py_proto_library( 411 name, 412 srcs = [], 413 deps = [], 414 py_libs = [], 415 py_extra_srcs = [], 416 include = None, 417 default_runtime = "@com_google_protobuf//:protobuf_python", 418 protoc = "@com_google_protobuf//:protoc", 419 use_grpc_plugin = False, 420 **kargs): 421 """Bazel rule to create a Python protobuf library from proto source files 422 423 NOTE: the rule is only an internal workaround to generate protos. The 424 interface may change and the rule may be removed when bazel has introduced 425 the native rule. 426 427 Args: 428 name: the name of the py_proto_library. 429 srcs: the .proto files of the py_proto_library. 430 deps: a list of dependency labels; must be py_proto_library. 431 py_libs: a list of other py_library targets depended by the generated 432 py_library. 433 py_extra_srcs: extra source files that will be added to the output 434 py_library. This attribute is used for internal bootstrapping. 435 include: a string indicating the include path of the .proto files. 436 default_runtime: the implicitly default runtime which will be depended on by 437 the generated py_library target. 438 protoc: the label of the protocol compiler to generate the sources. 439 use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin 440 when processing the proto files. 441 **kargs: other keyword arguments that are passed to py_library. 442 443 """ 444 outs = _PyOuts(srcs, use_grpc_plugin) 445 446 includes = [] 447 if include != None: 448 includes = [include] 449 450 grpc_python_plugin = None 451 if use_grpc_plugin: 452 grpc_python_plugin = "//external:grpc_python_plugin" 453 # Note: Generated grpc code depends on Python grpc module. This dependency 454 # is not explicitly listed in py_libs. Instead, host system is assumed to 455 # have grpc installed. 456 457 proto_gen( 458 name = name + "_genproto", 459 srcs = srcs, 460 deps = [s + "_genproto" for s in deps], 461 includes = includes, 462 protoc = protoc, 463 gen_py = 1, 464 outs = outs, 465 visibility = ["//visibility:public"], 466 plugin = grpc_python_plugin, 467 plugin_language = "grpc", 468 ) 469 470 if default_runtime and not default_runtime in py_libs + deps: 471 py_libs = py_libs + [default_runtime] 472 py_library( 473 name = name, 474 srcs = outs + py_extra_srcs, 475 deps = py_libs + deps, 476 imports = includes, 477 **kargs 478 ) 479 480def internal_protobuf_py_tests( 481 name, 482 modules = [], 483 **kargs): 484 """Bazel rules to create batch tests for protobuf internal. 485 486 Args: 487 name: the name of the rule. 488 modules: a list of modules for tests. The macro will create a py_test for 489 each of the parameter with the source "google/protobuf/%s.py" 490 kargs: extra parameters that will be passed into the py_test. 491 492 """ 493 for m in modules: 494 s = "python/google/protobuf/internal/%s.py" % m 495 py_test( 496 name = "py_%s" % m, 497 srcs = [s], 498 main = s, 499 **kargs 500 ) 501 502def check_protobuf_required_bazel_version(): 503 """For WORKSPACE files, to check the installed version of bazel. 504 505 This ensures bazel supports our approach to proto_library() depending on a 506 copied filegroup. (Fixed in bazel 0.5.4) 507 """ 508 versions.check(minimum_bazel_version = "0.5.4") 509