• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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