load("@bazel_skylib//lib:versions.bzl", "versions") load("@rules_cc//cc:defs.bzl", "cc_library") load("@rules_proto//proto:defs.bzl", "ProtoInfo") load("@rules_python//python:defs.bzl", "py_library", "py_test") def _GetPath(ctx, path): if ctx.label.workspace_root: return ctx.label.workspace_root + "/" + path else: return path def _IsNewExternal(ctx): # Bazel 0.4.4 and older have genfiles paths that look like: # bazel-out/local-fastbuild/genfiles/external/repo/foo # After the exec root rearrangement, they look like: # ../repo/bazel-out/local-fastbuild/genfiles/foo return ctx.label.workspace_root.startswith("../") def _GenDir(ctx): if _IsNewExternal(ctx): # We are using the fact that Bazel 0.4.4+ provides repository-relative paths # for ctx.genfiles_dir. return ctx.genfiles_dir.path + ( "/" + ctx.attr.includes[0] if ctx.attr.includes and ctx.attr.includes[0] else "" ) # This means that we're either in the old version OR the new version in the local repo. # Either way, appending the source path to the genfiles dir works. return ctx.var["GENDIR"] + "/" + _SourceDir(ctx) def _SourceDir(ctx): if not ctx.attr.includes: return ctx.label.workspace_root if not ctx.attr.includes[0]: return _GetPath(ctx, ctx.label.package) if not ctx.label.package: return _GetPath(ctx, ctx.attr.includes[0]) return _GetPath(ctx, ctx.label.package + "/" + ctx.attr.includes[0]) def _CcHdrs(srcs, use_grpc_plugin = False): ret = [s[:-len(".proto")] + ".pb.h" for s in srcs] if use_grpc_plugin: ret += [s[:-len(".proto")] + ".grpc.pb.h" for s in srcs] return ret def _CcSrcs(srcs, use_grpc_plugin = False): ret = [s[:-len(".proto")] + ".pb.cc" for s in srcs] if use_grpc_plugin: ret += [s[:-len(".proto")] + ".grpc.pb.cc" for s in srcs] return ret def _CcOuts(srcs, use_grpc_plugin = False): return _CcHdrs(srcs, use_grpc_plugin) + _CcSrcs(srcs, use_grpc_plugin) def _PyOuts(srcs, use_grpc_plugin = False): ret = [s[:-len(".proto")] + "_pb2.py" for s in srcs] if use_grpc_plugin: ret += [s[:-len(".proto")] + "_pb2_grpc.py" for s in srcs] return ret def _RelativeOutputPath(path, include, dest = ""): if include == None: return path if not path.startswith(include): fail("Include path %s isn't part of the path %s." % (include, path)) if include and include[-1] != "/": include = include + "/" if dest and dest[-1] != "/": dest = dest + "/" path = path[len(include):] return dest + path def _proto_gen_impl(ctx): """General implementation for generating protos""" srcs = ctx.files.srcs deps = depset(direct=ctx.files.srcs) source_dir = _SourceDir(ctx) gen_dir = _GenDir(ctx).rstrip("/") if source_dir: has_sources = any([src.is_source for src in srcs]) has_generated = any([not src.is_source for src in srcs]) import_flags = [] if has_sources: import_flags += ["-I" + source_dir] if has_generated: import_flags += ["-I" + gen_dir] import_flags = depset(direct=import_flags) else: import_flags = depset(direct=["-I."]) for dep in ctx.attr.deps: if type(dep.proto.import_flags) == "list": import_flags = depset(transitive=[import_flags], direct=dep.proto.import_flags) else: import_flags = depset(transitive=[import_flags, dep.proto.import_flags]) if type(dep.proto.deps) == "list": deps = depset(transitive=[deps], direct=dep.proto.deps) else: deps = depset(transitive=[deps, dep.proto.deps]) if not ctx.attr.gen_cc and not ctx.attr.gen_py and not ctx.executable.plugin: return struct( proto = struct( srcs = srcs, import_flags = import_flags, deps = deps, ), ) for src in srcs: args = [] in_gen_dir = src.root.path == gen_dir if in_gen_dir: import_flags_real = [] for f in import_flags.to_list(): path = f.replace("-I", "") import_flags_real.append("-I$(realpath -s %s)" % path) outs = [] use_grpc_plugin = (ctx.attr.plugin_language == "grpc" and ctx.attr.plugin) path_tpl = "$(realpath %s)" if in_gen_dir else "%s" if ctx.attr.gen_cc: args += [("--cpp_out=" + path_tpl) % gen_dir] outs.extend(_CcOuts([src.basename], use_grpc_plugin = use_grpc_plugin)) if ctx.attr.gen_py: args += [("--python_out=" + path_tpl) % gen_dir] outs.extend(_PyOuts([src.basename], use_grpc_plugin = use_grpc_plugin)) outs = [ctx.actions.declare_file(out, sibling = src) for out in outs] inputs = [src] + deps.to_list() tools = [ctx.executable.protoc] if ctx.executable.plugin: plugin = ctx.executable.plugin lang = ctx.attr.plugin_language if not lang and plugin.basename.startswith("protoc-gen-"): lang = plugin.basename[len("protoc-gen-"):] if not lang: fail("cannot infer the target language of plugin", "plugin_language") outdir = "." if in_gen_dir else gen_dir if ctx.attr.plugin_options: outdir = ",".join(ctx.attr.plugin_options) + ":" + outdir args += [("--plugin=protoc-gen-%s=" + path_tpl) % (lang, plugin.path)] args += ["--%s_out=%s" % (lang, outdir)] tools.append(plugin) if not in_gen_dir: ctx.actions.run( inputs = inputs, tools = tools, outputs = outs, arguments = args + import_flags.to_list() + [src.path], executable = ctx.executable.protoc, mnemonic = "ProtoCompile", use_default_shell_env = True, ) else: for out in outs: orig_command = " ".join( ["$(realpath %s)" % ctx.executable.protoc.path] + args + import_flags_real + ["-I.", src.basename], ) command = ";".join([ 'CMD="%s"' % orig_command, "cd %s" % src.dirname, "${CMD}", "cd -", ]) generated_out = "/".join([gen_dir, out.basename]) if generated_out != out.path: command += ";mv %s %s" % (generated_out, out.path) ctx.actions.run_shell( inputs = inputs, outputs = [out], command = command, mnemonic = "ProtoCompile", tools = tools, use_default_shell_env = True, ) return struct( proto = struct( srcs = srcs, import_flags = import_flags, deps = deps, ), ) proto_gen = rule( attrs = { "srcs": attr.label_list(allow_files = True), "deps": attr.label_list(providers = ["proto"]), "includes": attr.string_list(), "protoc": attr.label( cfg = "exec", executable = True, allow_single_file = True, mandatory = True, ), "plugin": attr.label( cfg = "exec", allow_files = True, executable = True, ), "plugin_language": attr.string(), "plugin_options": attr.string_list(), "gen_cc": attr.bool(), "gen_py": attr.bool(), "outs": attr.output_list(), }, output_to_genfiles = True, implementation = _proto_gen_impl, ) """Generates codes from Protocol Buffers definitions. This rule helps you to implement Skylark macros specific to the target language. You should prefer more specific `cc_proto_library `, `py_proto_library` and others unless you are adding such wrapper macros. Args: srcs: Protocol Buffers definition files (.proto) to run the protocol compiler against. deps: a list of dependency labels; must be other proto libraries. includes: a list of include paths to .proto files. protoc: the label of the protocol compiler to generate the sources. plugin: the label of the protocol compiler plugin to be passed to the protocol compiler. plugin_language: the language of the generated sources plugin_options: a list of options to be passed to the plugin gen_cc: generates C++ sources in addition to the ones from the plugin. gen_py: generates Python sources in addition to the ones from the plugin. outs: a list of labels of the expected outputs from the protocol compiler. """ def _adapt_proto_library_impl(ctx): deps = [dep[ProtoInfo] for dep in ctx.attr.deps] srcs = [src for dep in deps for src in dep.direct_sources] return struct( proto = struct( srcs = srcs, import_flags = ["-I{}".format(path) for dep in deps for path in dep.transitive_proto_path.to_list()], deps = srcs, ), ) adapt_proto_library = rule( implementation = _adapt_proto_library_impl, attrs = { "deps": attr.label_list( mandatory = True, providers = [ProtoInfo], ), }, doc = "Adapts `proto_library` from `@rules_proto` to be used with `{cc,py}_proto_library` from this file.", ) def cc_proto_library( name, srcs = [], deps = [], cc_libs = [], include = None, protoc = Label("//:protoc"), use_grpc_plugin = False, default_runtime = Label("//:protobuf"), **kargs): """Bazel rule to create a C++ protobuf library from proto source files NOTE: the rule is only an internal workaround to generate protos. The interface may change and the rule may be removed when bazel has introduced the native rule. Args: name: the name of the cc_proto_library. srcs: the .proto files of the cc_proto_library. deps: a list of dependency labels; must be cc_proto_library. cc_libs: a list of other cc_library targets depended by the generated cc_library. include: a string indicating the include path of the .proto files. protoc: the label of the protocol compiler to generate the sources. use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin when processing the proto files. default_runtime: the implicitly default runtime which will be depended on by the generated cc_library target. **kargs: other keyword arguments that are passed to cc_library. """ includes = [] if include != None: includes = [include] grpc_cpp_plugin = None if use_grpc_plugin: grpc_cpp_plugin = "//external:grpc_cpp_plugin" gen_srcs = _CcSrcs(srcs, use_grpc_plugin) gen_hdrs = _CcHdrs(srcs, use_grpc_plugin) outs = gen_srcs + gen_hdrs proto_gen( name = name + "_genproto", srcs = srcs, deps = [s + "_genproto" for s in deps], includes = includes, protoc = protoc, plugin = grpc_cpp_plugin, plugin_language = "grpc", gen_cc = 1, outs = outs, visibility = ["//visibility:public"], ) if default_runtime and not default_runtime in cc_libs: cc_libs = cc_libs + [default_runtime] if use_grpc_plugin: cc_libs = cc_libs + ["//external:grpc_lib"] cc_library( name = name, srcs = gen_srcs, hdrs = gen_hdrs, deps = cc_libs + deps, includes = includes, **kargs ) def _internal_gen_well_known_protos_java_impl(ctx): args = ctx.actions.args() deps = [d[ProtoInfo] for d in ctx.attr.deps] srcjar = ctx.actions.declare_file("{}.srcjar".format(ctx.attr.name)) if ctx.attr.javalite: java_out = "lite:%s" % srcjar.path else: java_out = srcjar args.add("--java_out", java_out) descriptors = depset( transitive = [dep.transitive_descriptor_sets for dep in deps], ) args.add_joined( "--descriptor_set_in", descriptors, join_with = ctx.configuration.host_path_separator, ) for dep in deps: if "." == dep.proto_source_root: args.add_all([src.path for src in dep.direct_sources]) else: source_root = dep.proto_source_root offset = len(source_root) + 1 # + '/'. args.add_all([src.path[offset:] for src in dep.direct_sources]) ctx.actions.run( executable = ctx.executable._protoc, inputs = descriptors, outputs = [srcjar], arguments = [args], use_default_shell_env = True, ) return [ DefaultInfo( files = depset([srcjar]), ), ] internal_gen_well_known_protos_java = rule( implementation = _internal_gen_well_known_protos_java_impl, attrs = { "deps": attr.label_list( mandatory = True, providers = [ProtoInfo], ), "javalite": attr.bool( default = False, ), "_protoc": attr.label( executable = True, cfg = "exec", default = "//:protoc", ), }, ) def internal_copied_filegroup(name, srcs, strip_prefix, dest, **kwargs): """Macro to copy files to a different directory and then create a filegroup. This is used by the //:protobuf_python py_proto_library target to work around an issue caused by Python source files that are part of the same Python package being in separate directories. Args: srcs: The source files to copy and add to the filegroup. strip_prefix: Path to the root of the files to copy. dest: The directory to copy the source files into. **kwargs: extra arguments that will be passesd to the filegroup. """ outs = [_RelativeOutputPath(s, strip_prefix, dest) for s in srcs] native.genrule( name = name + "_genrule", srcs = srcs, outs = outs, cmd = " && ".join( ["cp $(location %s) $(location %s)" % (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs], ), ) native.filegroup( name = name, srcs = outs, **kwargs ) def py_proto_library( name, srcs = [], deps = [], py_libs = [], py_extra_srcs = [], include = None, default_runtime = Label("//:protobuf_python"), protoc = Label("//:protoc"), use_grpc_plugin = False, **kargs): """Bazel rule to create a Python protobuf library from proto source files NOTE: the rule is only an internal workaround to generate protos. The interface may change and the rule may be removed when bazel has introduced the native rule. Args: name: the name of the py_proto_library. srcs: the .proto files of the py_proto_library. deps: a list of dependency labels; must be py_proto_library. py_libs: a list of other py_library targets depended by the generated py_library. py_extra_srcs: extra source files that will be added to the output py_library. This attribute is used for internal bootstrapping. include: a string indicating the include path of the .proto files. default_runtime: the implicitly default runtime which will be depended on by the generated py_library target. protoc: the label of the protocol compiler to generate the sources. use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin when processing the proto files. **kargs: other keyword arguments that are passed to py_library. """ outs = _PyOuts(srcs, use_grpc_plugin) includes = [] if include != None: includes = [include] grpc_python_plugin = None if use_grpc_plugin: grpc_python_plugin = "//external:grpc_python_plugin" # Note: Generated grpc code depends on Python grpc module. This dependency # is not explicitly listed in py_libs. Instead, host system is assumed to # have grpc installed. proto_gen( name = name + "_genproto", srcs = srcs, deps = [s + "_genproto" for s in deps], includes = includes, protoc = protoc, gen_py = 1, outs = outs, visibility = ["//visibility:public"], plugin = grpc_python_plugin, plugin_language = "grpc", ) if default_runtime and not default_runtime in py_libs + deps: py_libs = py_libs + [default_runtime] py_library( name = name, srcs = outs + py_extra_srcs, deps = py_libs + deps, imports = includes, **kargs ) def internal_protobuf_py_tests( name, modules = [], **kargs): """Bazel rules to create batch tests for protobuf internal. Args: name: the name of the rule. modules: a list of modules for tests. The macro will create a py_test for each of the parameter with the source "google/protobuf/%s.py" kargs: extra parameters that will be passed into the py_test. """ for m in modules: s = "python/google/protobuf/internal/%s.py" % m py_test( name = "py_%s" % m, srcs = [s], main = s, **kargs ) def check_protobuf_required_bazel_version(): """For WORKSPACE files, to check the installed version of bazel. This ensures bazel supports our approach to proto_library() depending on a copied filegroup. (Fixed in bazel 0.5.4) """ versions.check(minimum_bazel_version = "0.5.4")