• 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        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