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