• 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        has_sources = any([src.is_source for src in srcs])
84        has_generated = any([not src.is_source for src in srcs])
85        import_flags = []
86        if has_sources:
87            import_flags += ["-I" + source_dir]
88        if has_generated:
89            import_flags += ["-I" + gen_dir]
90        import_flags = depset(direct=import_flags)
91    else:
92        import_flags = depset(direct=["-I."])
93
94    for dep in ctx.attr.deps:
95        if type(dep.proto.import_flags) == "list":
96            import_flags = depset(transitive=[import_flags], direct=dep.proto.import_flags)
97        else:
98            import_flags = depset(transitive=[import_flags, dep.proto.import_flags])
99        if type(dep.proto.deps) == "list":
100            deps = depset(transitive=[deps], direct=dep.proto.deps)
101        else:
102            deps = depset(transitive=[deps, dep.proto.deps])
103
104    if not ctx.attr.gen_cc and not ctx.attr.gen_py and not ctx.executable.plugin:
105        return struct(
106            proto = struct(
107                srcs = srcs,
108                import_flags = import_flags,
109                deps = deps,
110            ),
111        )
112
113    for src in srcs:
114        args = []
115
116        in_gen_dir = src.root.path == gen_dir
117        if in_gen_dir:
118            import_flags_real = []
119            for f in import_flags.to_list():
120                path = f.replace("-I", "")
121                import_flags_real.append("-I$(realpath -s %s)" % path)
122
123        outs = []
124        use_grpc_plugin = (ctx.attr.plugin_language == "grpc" and ctx.attr.plugin)
125        path_tpl = "$(realpath %s)" if in_gen_dir else "%s"
126        if ctx.attr.gen_cc:
127            args += [("--cpp_out=" + path_tpl) % gen_dir]
128            outs.extend(_CcOuts([src.basename], use_grpc_plugin = use_grpc_plugin))
129        if ctx.attr.gen_py:
130            args += [("--python_out=" + path_tpl) % gen_dir]
131            outs.extend(_PyOuts([src.basename], use_grpc_plugin = use_grpc_plugin))
132
133        outs = [ctx.actions.declare_file(out, sibling = src) for out in outs]
134        inputs = [src] + deps.to_list()
135        tools = [ctx.executable.protoc]
136        if ctx.executable.plugin:
137            plugin = ctx.executable.plugin
138            lang = ctx.attr.plugin_language
139            if not lang and plugin.basename.startswith("protoc-gen-"):
140                lang = plugin.basename[len("protoc-gen-"):]
141            if not lang:
142                fail("cannot infer the target language of plugin", "plugin_language")
143
144            outdir = "." if in_gen_dir else gen_dir
145
146            if ctx.attr.plugin_options:
147                outdir = ",".join(ctx.attr.plugin_options) + ":" + outdir
148            args += [("--plugin=protoc-gen-%s=" + path_tpl) % (lang, plugin.path)]
149            args += ["--%s_out=%s" % (lang, outdir)]
150            tools.append(plugin)
151
152        if not in_gen_dir:
153            ctx.actions.run(
154                inputs = inputs,
155                tools = tools,
156                outputs = outs,
157                arguments = args + import_flags.to_list() + [src.path],
158                executable = ctx.executable.protoc,
159                mnemonic = "ProtoCompile",
160                use_default_shell_env = True,
161            )
162        else:
163            for out in outs:
164                orig_command = " ".join(
165                    ["$(realpath %s)" % ctx.executable.protoc.path] + args +
166                    import_flags_real + ["-I.", src.basename],
167                )
168                command = ";".join([
169                    'CMD="%s"' % orig_command,
170                    "cd %s" % src.dirname,
171                    "${CMD}",
172                    "cd -",
173                ])
174                generated_out = "/".join([gen_dir, out.basename])
175                if generated_out != out.path:
176                    command += ";mv %s %s" % (generated_out, out.path)
177                ctx.actions.run_shell(
178                    inputs = inputs,
179                    outputs = [out],
180                    command = command,
181                    mnemonic = "ProtoCompile",
182                    tools = tools,
183                    use_default_shell_env = True,
184                )
185
186    return struct(
187        proto = struct(
188            srcs = srcs,
189            import_flags = import_flags,
190            deps = deps,
191        ),
192    )
193
194proto_gen = rule(
195    attrs = {
196        "srcs": attr.label_list(allow_files = True),
197        "deps": attr.label_list(providers = ["proto"]),
198        "includes": attr.string_list(),
199        "protoc": attr.label(
200            cfg = "exec",
201            executable = True,
202            allow_single_file = True,
203            mandatory = True,
204        ),
205        "plugin": attr.label(
206            cfg = "exec",
207            allow_files = True,
208            executable = True,
209        ),
210        "plugin_language": attr.string(),
211        "plugin_options": attr.string_list(),
212        "gen_cc": attr.bool(),
213        "gen_py": attr.bool(),
214        "outs": attr.output_list(),
215    },
216    output_to_genfiles = True,
217    implementation = _proto_gen_impl,
218)
219"""Generates codes from Protocol Buffers definitions.
220
221This rule helps you to implement Skylark macros specific to the target
222language. You should prefer more specific `cc_proto_library `,
223`py_proto_library` and others unless you are adding such wrapper macros.
224
225Args:
226  srcs: Protocol Buffers definition files (.proto) to run the protocol compiler
227    against.
228  deps: a list of dependency labels; must be other proto libraries.
229  includes: a list of include paths to .proto files.
230  protoc: the label of the protocol compiler to generate the sources.
231  plugin: the label of the protocol compiler plugin to be passed to the protocol
232    compiler.
233  plugin_language: the language of the generated sources
234  plugin_options: a list of options to be passed to the plugin
235  gen_cc: generates C++ sources in addition to the ones from the plugin.
236  gen_py: generates Python sources in addition to the ones from the plugin.
237  outs: a list of labels of the expected outputs from the protocol compiler.
238"""
239
240def _adapt_proto_library_impl(ctx):
241    deps = [dep[ProtoInfo] for dep in ctx.attr.deps]
242
243    srcs = [src for dep in deps for src in dep.direct_sources]
244    return struct(
245        proto = struct(
246            srcs = srcs,
247            import_flags = ["-I{}".format(path) for dep in deps for path in dep.transitive_proto_path.to_list()],
248            deps = srcs,
249        ),
250    )
251
252adapt_proto_library = rule(
253    implementation = _adapt_proto_library_impl,
254    attrs = {
255        "deps": attr.label_list(
256            mandatory = True,
257            providers = [ProtoInfo],
258        ),
259    },
260    doc = "Adapts `proto_library` from `@rules_proto` to be used with `{cc,py}_proto_library` from this file.",
261)
262
263def cc_proto_library(
264        name,
265        srcs = [],
266        deps = [],
267        cc_libs = [],
268        include = None,
269        protoc = "@com_google_protobuf//:protoc",
270        use_grpc_plugin = False,
271        default_runtime = "@com_google_protobuf//:protobuf",
272        **kargs):
273    """Bazel rule to create a C++ protobuf library from proto source files
274
275    NOTE: the rule is only an internal workaround to generate protos. The
276    interface may change and the rule may be removed when bazel has introduced
277    the native rule.
278
279    Args:
280      name: the name of the cc_proto_library.
281      srcs: the .proto files of the cc_proto_library.
282      deps: a list of dependency labels; must be cc_proto_library.
283      cc_libs: a list of other cc_library targets depended by the generated
284          cc_library.
285      include: a string indicating the include path of the .proto files.
286      protoc: the label of the protocol compiler to generate the sources.
287      use_grpc_plugin: a flag to indicate whether to call the grpc C++ plugin
288          when processing the proto files.
289      default_runtime: the implicitly default runtime which will be depended on by
290          the generated cc_library target.
291      **kargs: other keyword arguments that are passed to cc_library.
292    """
293
294    includes = []
295    if include != None:
296        includes = [include]
297
298    grpc_cpp_plugin = None
299    if use_grpc_plugin:
300        grpc_cpp_plugin = "//external:grpc_cpp_plugin"
301
302    gen_srcs = _CcSrcs(srcs, use_grpc_plugin)
303    gen_hdrs = _CcHdrs(srcs, use_grpc_plugin)
304    outs = gen_srcs + gen_hdrs
305
306    proto_gen(
307        name = name + "_genproto",
308        srcs = srcs,
309        deps = [s + "_genproto" for s in deps],
310        includes = includes,
311        protoc = protoc,
312        plugin = grpc_cpp_plugin,
313        plugin_language = "grpc",
314        gen_cc = 1,
315        outs = outs,
316        visibility = ["//visibility:public"],
317    )
318
319    if default_runtime and not default_runtime in cc_libs:
320        cc_libs = cc_libs + [default_runtime]
321    if use_grpc_plugin:
322        cc_libs = cc_libs + ["//external:grpc_lib"]
323    cc_library(
324        name = name,
325        srcs = gen_srcs,
326        hdrs = gen_hdrs,
327        deps = cc_libs + deps,
328        includes = includes,
329        **kargs
330    )
331
332def _internal_gen_well_known_protos_java_impl(ctx):
333    args = ctx.actions.args()
334
335    deps = [d[ProtoInfo] for d in ctx.attr.deps]
336
337    srcjar = ctx.actions.declare_file("{}.srcjar".format(ctx.attr.name))
338    if ctx.attr.javalite:
339        java_out = "lite:%s" % srcjar.path
340    else:
341        java_out = srcjar
342
343    args.add("--java_out", java_out)
344
345    descriptors = depset(
346        transitive = [dep.transitive_descriptor_sets for dep in deps],
347    )
348    args.add_joined(
349        "--descriptor_set_in",
350        descriptors,
351        join_with = ctx.configuration.host_path_separator,
352    )
353
354    for dep in deps:
355        if "." == dep.proto_source_root:
356            args.add_all([src.path for src in dep.direct_sources])
357        else:
358            source_root = dep.proto_source_root
359            offset = len(source_root) + 1  # + '/'.
360            args.add_all([src.path[offset:] for src in dep.direct_sources])
361
362    ctx.actions.run(
363        executable = ctx.executable._protoc,
364        inputs = descriptors,
365        outputs = [srcjar],
366        arguments = [args],
367        use_default_shell_env = True,
368    )
369
370    return [
371        DefaultInfo(
372            files = depset([srcjar]),
373        ),
374    ]
375
376internal_gen_well_known_protos_java = rule(
377    implementation = _internal_gen_well_known_protos_java_impl,
378    attrs = {
379        "deps": attr.label_list(
380            mandatory = True,
381            providers = [ProtoInfo],
382        ),
383        "javalite": attr.bool(
384            default = False,
385        ),
386        "_protoc": attr.label(
387            executable = True,
388            cfg = "exec",
389            default = "@com_google_protobuf//:protoc",
390        ),
391    },
392)
393
394def _internal_gen_kt_protos(ctx):
395    args = ctx.actions.args()
396
397    deps = [d[ProtoInfo] for d in ctx.attr.deps]
398
399    srcjar = ctx.actions.declare_file("{}.srcjar".format(ctx.attr.name))
400    if ctx.attr.lite:
401        out = "lite:%s" % srcjar.path
402    else:
403        out = srcjar
404
405    args.add("--kotlin_out", out)
406
407    descriptors = depset(
408        transitive = [dep.transitive_descriptor_sets for dep in deps],
409    )
410    args.add_joined(
411        "--descriptor_set_in",
412        descriptors,
413        join_with = ctx.configuration.host_path_separator,
414    )
415
416    for dep in deps:
417        if "." == dep.proto_source_root:
418            args.add_all([src.path for src in dep.direct_sources])
419        else:
420            source_root = dep.proto_source_root
421            offset = len(source_root) + 1  # + '/'.
422            args.add_all([src.path[offset:] for src in dep.direct_sources])
423
424    ctx.actions.run(
425        executable = ctx.executable._protoc,
426        inputs = descriptors,
427        outputs = [srcjar],
428        arguments = [args],
429        use_default_shell_env = True,
430    )
431
432    return [
433        DefaultInfo(
434            files = depset([srcjar]),
435        ),
436    ]
437
438internal_gen_kt_protos = rule(
439    implementation = _internal_gen_kt_protos,
440    attrs = {
441        "deps": attr.label_list(
442            mandatory = True,
443            providers = [ProtoInfo],
444        ),
445        "lite": attr.bool(
446            default = False,
447        ),
448        "_protoc": attr.label(
449            executable = True,
450            cfg = "exec",
451            default = "//:protoc",
452        ),
453    },
454)
455
456
457
458def internal_copied_filegroup(name, srcs, strip_prefix, dest, **kwargs):
459    """Macro to copy files to a different directory and then create a filegroup.
460
461    This is used by the //:protobuf_python py_proto_library target to work around
462    an issue caused by Python source files that are part of the same Python
463    package being in separate directories.
464
465    Args:
466      srcs: The source files to copy and add to the filegroup.
467      strip_prefix: Path to the root of the files to copy.
468      dest: The directory to copy the source files into.
469      **kwargs: extra arguments that will be passesd to the filegroup.
470    """
471    outs = [_RelativeOutputPath(s, strip_prefix, dest) for s in srcs]
472
473    native.genrule(
474        name = name + "_genrule",
475        srcs = srcs,
476        outs = outs,
477        cmd = " && ".join(
478            ["cp $(location %s) $(location %s)" %
479             (s, _RelativeOutputPath(s, strip_prefix, dest)) for s in srcs],
480        ),
481    )
482
483    native.filegroup(
484        name = name,
485        srcs = outs,
486        **kwargs
487    )
488
489def py_proto_library(
490        name,
491        srcs = [],
492        deps = [],
493        py_libs = [],
494        py_extra_srcs = [],
495        include = None,
496        default_runtime = "@com_google_protobuf//:protobuf_python",
497        protoc = "@com_google_protobuf//:protoc",
498        use_grpc_plugin = False,
499        **kargs):
500    """Bazel rule to create a Python protobuf library from proto source files
501
502    NOTE: the rule is only an internal workaround to generate protos. The
503    interface may change and the rule may be removed when bazel has introduced
504    the native rule.
505
506    Args:
507      name: the name of the py_proto_library.
508      srcs: the .proto files of the py_proto_library.
509      deps: a list of dependency labels; must be py_proto_library.
510      py_libs: a list of other py_library targets depended by the generated
511          py_library.
512      py_extra_srcs: extra source files that will be added to the output
513          py_library. This attribute is used for internal bootstrapping.
514      include: a string indicating the include path of the .proto files.
515      default_runtime: the implicitly default runtime which will be depended on by
516          the generated py_library target.
517      protoc: the label of the protocol compiler to generate the sources.
518      use_grpc_plugin: a flag to indicate whether to call the Python C++ plugin
519          when processing the proto files.
520      **kargs: other keyword arguments that are passed to py_library.
521
522    """
523    outs = _PyOuts(srcs, use_grpc_plugin)
524
525    includes = []
526    if include != None:
527        includes = [include]
528
529    grpc_python_plugin = None
530    if use_grpc_plugin:
531        grpc_python_plugin = "//external:grpc_python_plugin"
532        # Note: Generated grpc code depends on Python grpc module. This dependency
533        # is not explicitly listed in py_libs. Instead, host system is assumed to
534        # have grpc installed.
535
536    proto_gen(
537        name = name + "_genproto",
538        srcs = srcs,
539        deps = [s + "_genproto" for s in deps],
540        includes = includes,
541        protoc = protoc,
542        gen_py = 1,
543        outs = outs,
544        visibility = ["//visibility:public"],
545        plugin = grpc_python_plugin,
546        plugin_language = "grpc",
547    )
548
549    if default_runtime and not default_runtime in py_libs + deps:
550        py_libs = py_libs + [default_runtime]
551    py_library(
552        name = name,
553        srcs = outs + py_extra_srcs,
554        deps = py_libs + deps,
555        imports = includes,
556        **kargs
557    )
558
559def internal_protobuf_py_tests(
560        name,
561        modules = [],
562        **kargs):
563    """Bazel rules to create batch tests for protobuf internal.
564
565    Args:
566      name: the name of the rule.
567      modules: a list of modules for tests. The macro will create a py_test for
568          each of the parameter with the source "google/protobuf/%s.py"
569      kargs: extra parameters that will be passed into the py_test.
570
571    """
572    for m in modules:
573        s = "python/google/protobuf/internal/%s.py" % m
574        py_test(
575            name = "py_%s" % m,
576            srcs = [s],
577            main = s,
578            **kargs
579        )
580
581def check_protobuf_required_bazel_version():
582    """For WORKSPACE files, to check the installed version of bazel.
583
584    This ensures bazel supports our approach to proto_library() depending on a
585    copied filegroup. (Fixed in bazel 0.5.4)
586    """
587    versions.check(minimum_bazel_version = "0.5.4")
588