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