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