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