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