1# Copyright (c) 2009-2021, Google LLC
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#     * Redistributions of source code must retain the above copyright
7#       notice, this list of conditions and the following disclaimer.
8#     * Redistributions in binary form must reproduce the above copyright
9#       notice, this list of conditions and the following disclaimer in the
10#       documentation and/or other materials provided with the distribution.
11#     * Neither the name of Google LLC nor the
12#       names of its contributors may be used to endorse or promote products
13#       derived from this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18# DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY
19# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26"""Public rules for using upb protos:
27  - upb_proto_library()
28  - upb_proto_reflection_library()
29"""
30
31load("@bazel_skylib//lib:paths.bzl", "paths")
32
33# begin:google_only
34# load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
35# end:google_only
36
37# begin:github_only
38# Compatibility code for Bazel 4.x. Remove this when we drop support for Bazel 4.x.
39load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
40
41def use_cpp_toolchain():
42    return ["@bazel_tools//tools/cpp:toolchain_type"]
43# end:github_only
44
45# Generic support code #########################################################
46
47# begin:github_only
48_is_google3 = False
49# end:github_only
50
51# begin:google_only
52# _is_google3 = True
53# end:google_only
54
55def _get_real_short_path(file):
56    # For some reason, files from other archives have short paths that look like:
57    #   ../com_google_protobuf/google/protobuf/descriptor.proto
58    short_path = file.short_path
59    if short_path.startswith("../"):
60        second_slash = short_path.index("/", 3)
61        short_path = short_path[second_slash + 1:]
62
63    # Sometimes it has another few prefixes like:
64    #   _virtual_imports/any_proto/google/protobuf/any.proto
65    #   benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto
66    # We want just google/protobuf/any.proto.
67    virtual_imports = "_virtual_imports/"
68    if virtual_imports in short_path:
69        short_path = short_path.split(virtual_imports)[1].split("/", 1)[1]
70    return short_path
71
72def _get_real_root(ctx, file):
73    real_short_path = _get_real_short_path(file)
74    root = file.path[:-len(real_short_path) - 1]
75
76    if not _is_google3 and ctx.rule.attr.strip_import_prefix:
77        root = paths.join(root, ctx.rule.attr.strip_import_prefix[1:])
78    return root
79
80def _generate_output_file(ctx, src, extension):
81    package = ctx.label.package
82    if not _is_google3:
83        strip_import_prefix = ctx.rule.attr.strip_import_prefix
84        if strip_import_prefix and strip_import_prefix != "/":
85            if not package.startswith(strip_import_prefix[1:]):
86                fail("%s does not begin with prefix %s" % (package, strip_import_prefix))
87            package = package[len(strip_import_prefix):]
88
89    real_short_path = _get_real_short_path(src)
90    real_short_path = paths.relativize(real_short_path, package)
91    output_filename = paths.replace_extension(real_short_path, extension)
92    ret = ctx.actions.declare_file(output_filename)
93    return ret
94
95def _generate_include_path(src, out, extension):
96    short_path = _get_real_short_path(src)
97    short_path = paths.replace_extension(short_path, extension)
98    if not out.path.endswith(short_path):
99        fail("%s does not end with %s" % (out.path, short_path))
100
101    return out.path[:-len(short_path)]
102
103def _filter_none(elems):
104    out = []
105    for elem in elems:
106        if elem:
107            out.append(elem)
108    return out
109
110def _cc_library_func(ctx, name, hdrs, srcs, copts, includes, dep_ccinfos):
111    """Like cc_library(), but callable from rules.
112
113    Args:
114      ctx: Rule context.
115      name: Unique name used to generate output files.
116      hdrs: Public headers that can be #included from other rules.
117      srcs: C/C++ source files.
118      copts: Additional options for cc compilation.
119      includes: Additional include paths.
120      dep_ccinfos: CcInfo providers of dependencies we should build/link against.
121
122    Returns:
123      CcInfo provider for this compilation.
124    """
125
126    compilation_contexts = [info.compilation_context for info in dep_ccinfos]
127    linking_contexts = [info.linking_context for info in dep_ccinfos]
128    toolchain = find_cpp_toolchain(ctx)
129    feature_configuration = cc_common.configure_features(
130        ctx = ctx,
131        cc_toolchain = toolchain,
132        requested_features = ctx.features,
133        unsupported_features = ctx.disabled_features,
134    )
135
136    blaze_only_args = {}
137
138    if _is_google3:
139        blaze_only_args["grep_includes"] = ctx.file._grep_includes
140
141    (compilation_context, compilation_outputs) = cc_common.compile(
142        actions = ctx.actions,
143        feature_configuration = feature_configuration,
144        cc_toolchain = toolchain,
145        name = name,
146        srcs = srcs,
147        includes = includes,
148        public_hdrs = hdrs,
149        user_compile_flags = copts,
150        compilation_contexts = compilation_contexts,
151        **blaze_only_args
152    )
153
154    # buildifier: disable=unused-variable
155    (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
156        actions = ctx.actions,
157        name = name,
158        feature_configuration = feature_configuration,
159        cc_toolchain = toolchain,
160        compilation_outputs = compilation_outputs,
161        linking_contexts = linking_contexts,
162        disallow_dynamic_library = cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "targets_windows"),
163        **blaze_only_args
164    )
165
166    return CcInfo(
167        compilation_context = compilation_context,
168        linking_context = linking_context,
169    )
170
171# Dummy rule to expose select() copts to aspects  ##############################
172
173UpbProtoLibraryCoptsInfo = provider(
174    "Provides copts for upb proto targets",
175    fields = {
176        "copts": "copts for upb_proto_library()",
177    },
178)
179
180def upb_proto_library_copts_impl(ctx):
181    return UpbProtoLibraryCoptsInfo(copts = ctx.attr.copts)
182
183upb_proto_library_copts = rule(
184    implementation = upb_proto_library_copts_impl,
185    attrs = {"copts": attr.string_list(default = [])},
186)
187
188# upb_proto_library / upb_proto_reflection_library shared code #################
189
190GeneratedSrcsInfo = provider(
191    "Provides generated headers and sources",
192    fields = {
193        "srcs": "list of srcs",
194        "hdrs": "list of hdrs",
195        "thunks": "Experimental, do not use. List of srcs defining C API. Incompatible with hdrs.",
196        "includes": "list of extra includes",
197    },
198)
199
200UpbWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info", "cc_info_with_thunks"])
201_UpbDefsWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info"])
202_UpbWrappedGeneratedSrcsInfo = provider("Provider for generated sources", fields = ["srcs"])
203_WrappedDefsGeneratedSrcsInfo = provider(
204    "Provider for generated reflective sources",
205    fields = ["srcs"],
206)
207
208def _compile_upb_protos(ctx, generator, proto_info, proto_sources):
209    if len(proto_sources) == 0:
210        return GeneratedSrcsInfo(srcs = [], hdrs = [], thunks = [], includes = [])
211
212    ext = "." + generator
213    tool = getattr(ctx.executable, "_gen_" + generator)
214    srcs = [_generate_output_file(ctx, name, ext + ".c") for name in proto_sources]
215    hdrs = [_generate_output_file(ctx, name, ext + ".h") for name in proto_sources]
216    thunks = []
217    if generator == "upb":
218        thunks = [_generate_output_file(ctx, name, ext + ".thunks.c") for name in proto_sources]
219    transitive_sets = proto_info.transitive_descriptor_sets.to_list()
220
221    args = ctx.actions.args()
222    args.use_param_file(param_file_arg = "@%s")
223    args.set_param_file_format("multiline")
224
225    args.add("--" + generator + "_out=" + _get_real_root(ctx, srcs[0]))
226    args.add("--plugin=protoc-gen-" + generator + "=" + tool.path)
227    args.add("--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]))
228    args.add_all(proto_sources, map_each = _get_real_short_path)
229
230    ctx.actions.run(
231        inputs = depset(
232            direct = [proto_info.direct_descriptor_set],
233            transitive = [proto_info.transitive_descriptor_sets],
234        ),
235        tools = [tool],
236        outputs = srcs + hdrs,
237        executable = ctx.executable._protoc,
238        arguments = [args],
239        progress_message = "Generating upb protos for :" + ctx.label.name,
240        mnemonic = "GenUpbProtos",
241    )
242    if generator == "upb":
243        ctx.actions.run_shell(
244            inputs = hdrs,
245            outputs = thunks,
246            command = " && ".join([
247                "sed 's/UPB_INLINE //' {} > {}".format(hdr.path, thunk.path)
248                for (hdr, thunk) in zip(hdrs, thunks)
249            ]),
250            progress_message = "Generating thunks for upb protos API for: " + ctx.label.name,
251            mnemonic = "GenUpbProtosThunks",
252        )
253    return GeneratedSrcsInfo(
254        srcs = srcs,
255        hdrs = hdrs,
256        thunks = thunks,
257        includes = [_generate_include_path(proto_sources[0], hdrs[0], ext + ".h")],
258    )
259
260def _upb_proto_rule_impl(ctx):
261    if len(ctx.attr.deps) != 1:
262        fail("only one deps dependency allowed.")
263    dep = ctx.attr.deps[0]
264
265    if _WrappedDefsGeneratedSrcsInfo in dep:
266        srcs = dep[_WrappedDefsGeneratedSrcsInfo].srcs
267    elif _UpbWrappedGeneratedSrcsInfo in dep:
268        srcs = dep[_UpbWrappedGeneratedSrcsInfo].srcs
269    else:
270        fail("proto_library rule must generate _UpbWrappedGeneratedSrcsInfo or " +
271             "_WrappedDefsGeneratedSrcsInfo (aspect should have handled this).")
272
273    if _UpbDefsWrappedCcInfo in dep:
274        cc_info = dep[_UpbDefsWrappedCcInfo].cc_info
275    elif UpbWrappedCcInfo in dep:
276        cc_info = dep[UpbWrappedCcInfo].cc_info
277    else:
278        fail("proto_library rule must generate UpbWrappedCcInfo or " +
279             "_UpbDefsWrappedCcInfo (aspect should have handled this).")
280
281    lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0]
282    files = _filter_none([
283        lib.static_library,
284        lib.pic_static_library,
285        lib.dynamic_library,
286    ])
287    return [
288        DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)),
289        srcs,
290        cc_info,
291    ]
292
293def _upb_proto_aspect_impl(target, ctx, generator, cc_provider, file_provider):
294    proto_info = target[ProtoInfo]
295    files = _compile_upb_protos(ctx, generator, proto_info, proto_info.direct_sources)
296    deps = ctx.rule.attr.deps + getattr(ctx.attr, "_" + generator)
297    dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep]
298    dep_ccinfos += [dep[UpbWrappedCcInfo].cc_info for dep in deps if UpbWrappedCcInfo in dep]
299    dep_ccinfos += [dep[_UpbDefsWrappedCcInfo].cc_info for dep in deps if _UpbDefsWrappedCcInfo in dep]
300    if generator == "upbdefs":
301        if UpbWrappedCcInfo not in target:
302            fail("Target should have UpbWrappedCcInfo provider")
303        dep_ccinfos.append(target[UpbWrappedCcInfo].cc_info)
304    cc_info = _cc_library_func(
305        ctx = ctx,
306        name = ctx.rule.attr.name + "." + generator,
307        hdrs = files.hdrs,
308        srcs = files.srcs,
309        includes = files.includes,
310        copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts,
311        dep_ccinfos = dep_ccinfos,
312    )
313
314    if files.thunks:
315        cc_info_with_thunks = _cc_library_func(
316            ctx = ctx,
317            name = ctx.rule.attr.name + "." + generator + ".thunks",
318            hdrs = [],
319            srcs = files.thunks,
320            includes = files.includes,
321            copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts,
322            dep_ccinfos = dep_ccinfos + [cc_info],
323        )
324        wrapped_cc_info = cc_provider(
325            cc_info = cc_info,
326            cc_info_with_thunks = cc_info_with_thunks,
327        )
328    else:
329        wrapped_cc_info = cc_provider(
330            cc_info = cc_info,
331        )
332    return [
333        wrapped_cc_info,
334        file_provider(srcs = files),
335    ]
336
337def upb_proto_library_aspect_impl(target, ctx):
338    return _upb_proto_aspect_impl(target, ctx, "upb", UpbWrappedCcInfo, _UpbWrappedGeneratedSrcsInfo)
339
340def _upb_proto_reflection_library_aspect_impl(target, ctx):
341    return _upb_proto_aspect_impl(target, ctx, "upbdefs", _UpbDefsWrappedCcInfo, _WrappedDefsGeneratedSrcsInfo)
342
343def _maybe_add(d):
344    if _is_google3:
345        d["_grep_includes"] = attr.label(
346            allow_single_file = True,
347            cfg = "exec",
348            default = "@bazel_tools//tools/cpp:grep-includes",
349        )
350    return d
351
352# upb_proto_library() ##########################################################
353
354upb_proto_library_aspect = aspect(
355    attrs = _maybe_add({
356        "_copts": attr.label(
357            default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use",
358        ),
359        "_gen_upb": attr.label(
360            executable = True,
361            cfg = "exec",
362            default = "//upbc:protoc-gen-upb_stage1",
363        ),
364        "_protoc": attr.label(
365            executable = True,
366            cfg = "exec",
367            default = "@com_google_protobuf//:protoc",
368        ),
369        "_cc_toolchain": attr.label(
370            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
371        ),
372        "_upb": attr.label_list(default = [
373            "//:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
374        ]),
375        "_fasttable_enabled": attr.label(default = "//:fasttable_enabled"),
376    }),
377    implementation = upb_proto_library_aspect_impl,
378    provides = [
379        UpbWrappedCcInfo,
380        _UpbWrappedGeneratedSrcsInfo,
381    ],
382    attr_aspects = ["deps"],
383    fragments = ["cpp"],
384    toolchains = use_cpp_toolchain(),
385    incompatible_use_toolchain_transition = True,
386)
387
388upb_proto_library = rule(
389    output_to_genfiles = True,
390    implementation = _upb_proto_rule_impl,
391    attrs = {
392        "deps": attr.label_list(
393            aspects = [upb_proto_library_aspect],
394            allow_rules = ["proto_library"],
395            providers = [ProtoInfo],
396        ),
397    },
398)
399
400# upb_proto_reflection_library() ###############################################
401
402_upb_proto_reflection_library_aspect = aspect(
403    attrs = _maybe_add({
404        "_copts": attr.label(
405            default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use",
406        ),
407        "_gen_upbdefs": attr.label(
408            executable = True,
409            cfg = "exec",
410            default = "//upbc:protoc-gen-upbdefs",
411        ),
412        "_protoc": attr.label(
413            executable = True,
414            cfg = "exec",
415            default = "@com_google_protobuf//:protoc",
416        ),
417        "_cc_toolchain": attr.label(
418            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
419        ),
420        "_upbdefs": attr.label_list(
421            default = [
422                "//:generated_reflection_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
423            ],
424        ),
425    }),
426    implementation = _upb_proto_reflection_library_aspect_impl,
427    provides = [
428        _UpbDefsWrappedCcInfo,
429        _WrappedDefsGeneratedSrcsInfo,
430    ],
431    required_aspect_providers = [
432        UpbWrappedCcInfo,
433        _UpbWrappedGeneratedSrcsInfo,
434    ],
435    attr_aspects = ["deps"],
436    fragments = ["cpp"],
437    toolchains = use_cpp_toolchain(),
438    incompatible_use_toolchain_transition = True,
439)
440
441upb_proto_reflection_library = rule(
442    output_to_genfiles = True,
443    implementation = _upb_proto_rule_impl,
444    attrs = {
445        "deps": attr.label_list(
446            aspects = [
447                upb_proto_library_aspect,
448                _upb_proto_reflection_library_aspect,
449            ],
450            allow_rules = ["proto_library"],
451            providers = [ProtoInfo],
452        ),
453    },
454)
455