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")
32load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
33load("@rules_proto//proto:defs.bzl", "ProtoInfo")  # copybara:strip_for_google3
34
35# Generic support code #########################################################
36
37_is_bazel = True  # copybara:replace_for_google3 _is_bazel = False
38
39def _get_real_short_path(file):
40    # For some reason, files from other archives have short paths that look like:
41    #   ../com_google_protobuf/google/protobuf/descriptor.proto
42    short_path = file.short_path
43    if short_path.startswith("../"):
44        second_slash = short_path.index("/", 3)
45        short_path = short_path[second_slash + 1:]
46
47    # Sometimes it has another few prefixes like:
48    #   _virtual_imports/any_proto/google/protobuf/any.proto
49    #   benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto
50    # We want just google/protobuf/any.proto.
51    virtual_imports = "_virtual_imports/"
52    if virtual_imports in short_path:
53        short_path = short_path.split(virtual_imports)[1].split("/", 1)[1]
54    return short_path
55
56def _get_real_root(file):
57    real_short_path = _get_real_short_path(file)
58    return file.path[:-len(real_short_path) - 1]
59
60def _generate_output_file(ctx, src, extension):
61    real_short_path = _get_real_short_path(src)
62    real_short_path = paths.relativize(real_short_path, ctx.label.package)
63    output_filename = paths.replace_extension(real_short_path, extension)
64    ret = ctx.actions.declare_file(output_filename)
65    return ret
66
67def _filter_none(elems):
68    out = []
69    for elem in elems:
70        if elem:
71            out.append(elem)
72    return out
73
74def _cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos):
75    """Like cc_library(), but callable from rules.
76
77    Args:
78      ctx: Rule context.
79      name: Unique name used to generate output files.
80      hdrs: Public headers that can be #included from other rules.
81      srcs: C/C++ source files.
82      dep_ccinfos: CcInfo providers of dependencies we should build/link against.
83
84    Returns:
85      CcInfo provider for this compilation.
86    """
87
88    compilation_contexts = [info.compilation_context for info in dep_ccinfos]
89    linking_contexts = [info.linking_context for info in dep_ccinfos]
90    toolchain = find_cpp_toolchain(ctx)
91    feature_configuration = cc_common.configure_features(
92        ctx = ctx,
93        cc_toolchain = toolchain,
94        requested_features = ctx.features,
95        unsupported_features = ctx.disabled_features,
96    )
97
98    blaze_only_args = {}
99
100    if not _is_bazel:
101        blaze_only_args["grep_includes"] = ctx.file._grep_includes
102
103    (compilation_context, compilation_outputs) = cc_common.compile(
104        actions = ctx.actions,
105        feature_configuration = feature_configuration,
106        cc_toolchain = toolchain,
107        name = name,
108        srcs = srcs,
109        public_hdrs = hdrs,
110        user_compile_flags = copts,
111        compilation_contexts = compilation_contexts,
112        **blaze_only_args
113    )
114    (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
115        actions = ctx.actions,
116        name = name,
117        feature_configuration = feature_configuration,
118        cc_toolchain = toolchain,
119        compilation_outputs = compilation_outputs,
120        linking_contexts = linking_contexts,
121        **blaze_only_args
122    )
123
124    return CcInfo(
125        compilation_context = compilation_context,
126        linking_context = linking_context,
127    )
128
129# Build setting for whether fasttable code generation is enabled ###############
130
131_FastTableEnabled = provider(
132    fields = {
133        "enabled": "whether fasttable is enabled",
134    },
135)
136
137def fasttable_enabled_impl(ctx):
138    raw_setting = ctx.build_setting_value
139
140    if raw_setting:
141        # TODO(haberman): check that the target CPU supports fasttable.
142        pass
143
144    return _FastTableEnabled(enabled = raw_setting)
145
146upb_fasttable_enabled = rule(
147    implementation = fasttable_enabled_impl,
148    build_setting = config.bool(flag = True),
149)
150
151# Dummy rule to expose select() copts to aspects  ##############################
152
153_UpbProtoLibraryCopts = provider(
154    fields = {
155        "copts": "copts for upb_proto_library()",
156    },
157)
158
159def upb_proto_library_copts_impl(ctx):
160    return _UpbProtoLibraryCopts(copts = ctx.attr.copts)
161
162upb_proto_library_copts = rule(
163    implementation = upb_proto_library_copts_impl,
164    attrs = {"copts": attr.string_list(default = [])},
165)
166
167# upb_proto_library / upb_proto_reflection_library shared code #################
168
169GeneratedSrcsInfo = provider(
170    fields = {
171        "srcs": "list of srcs",
172        "hdrs": "list of hdrs",
173    },
174)
175
176_UpbWrappedCcInfo = provider(fields = ["cc_info"])
177_UpbDefsWrappedCcInfo = provider(fields = ["cc_info"])
178_WrappedGeneratedSrcsInfo = provider(fields = ["srcs"])
179_WrappedDefsGeneratedSrcsInfo = provider(fields = ["srcs"])
180
181def _compile_upb_protos(ctx, generator, proto_info, proto_sources):
182    if len(proto_sources) == 0:
183        return GeneratedSrcsInfo(srcs = [], hdrs = [])
184
185    ext = "." + generator
186    tool = getattr(ctx.executable, "_gen_" + generator)
187    srcs = [_generate_output_file(ctx, name, ext + ".c") for name in proto_sources]
188    hdrs = [_generate_output_file(ctx, name, ext + ".h") for name in proto_sources]
189    transitive_sets = proto_info.transitive_descriptor_sets.to_list()
190    fasttable_enabled = (hasattr(ctx.attr, "_fasttable_enabled") and
191                         ctx.attr._fasttable_enabled[_FastTableEnabled].enabled)
192    codegen_params = "fasttable:" if fasttable_enabled else ""
193    ctx.actions.run(
194        inputs = depset(
195            direct = [proto_info.direct_descriptor_set],
196            transitive = [proto_info.transitive_descriptor_sets],
197        ),
198        tools = [tool],
199        outputs = srcs + hdrs,
200        executable = ctx.executable._protoc,
201        arguments = [
202                        "--" + generator + "_out=" + codegen_params + _get_real_root(srcs[0]),
203                        "--plugin=protoc-gen-" + generator + "=" + tool.path,
204                        "--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]),
205                    ] +
206                    [_get_real_short_path(file) for file in proto_sources],
207        progress_message = "Generating upb protos for :" + ctx.label.name,
208    )
209    return GeneratedSrcsInfo(srcs = srcs, hdrs = hdrs)
210
211def _upb_proto_rule_impl(ctx):
212    if len(ctx.attr.deps) != 1:
213        fail("only one deps dependency allowed.")
214    dep = ctx.attr.deps[0]
215
216    if _WrappedDefsGeneratedSrcsInfo in dep:
217        srcs = dep[_WrappedDefsGeneratedSrcsInfo].srcs
218    elif _WrappedGeneratedSrcsInfo in dep:
219        srcs = dep[_WrappedGeneratedSrcsInfo].srcs
220    else:
221        fail("proto_library rule must generate _WrappedGeneratedSrcsInfo or " +
222             "_WrappedDefsGeneratedSrcsInfo (aspect should have handled this).")
223
224    if _UpbDefsWrappedCcInfo in dep:
225        cc_info = dep[_UpbDefsWrappedCcInfo].cc_info
226    elif _UpbWrappedCcInfo in dep:
227        cc_info = dep[_UpbWrappedCcInfo].cc_info
228    else:
229        fail("proto_library rule must generate _UpbWrappedCcInfo or " +
230             "_UpbDefsWrappedCcInfo (aspect should have handled this).")
231
232    lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0]
233    files = _filter_none([
234        lib.static_library,
235        lib.pic_static_library,
236        lib.dynamic_library,
237    ])
238    return [
239        DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)),
240        srcs,
241        cc_info,
242    ]
243
244def _upb_proto_aspect_impl(target, ctx, generator, cc_provider, file_provider):
245    proto_info = target[ProtoInfo]
246    files = _compile_upb_protos(ctx, generator, proto_info, proto_info.direct_sources)
247    deps = ctx.rule.attr.deps + getattr(ctx.attr, "_" + generator)
248    dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep]
249    dep_ccinfos += [dep[_UpbWrappedCcInfo].cc_info for dep in deps if _UpbWrappedCcInfo in dep]
250    dep_ccinfos += [dep[_UpbDefsWrappedCcInfo].cc_info for dep in deps if _UpbDefsWrappedCcInfo in dep]
251    if generator == "upbdefs":
252        if _UpbWrappedCcInfo not in target:
253            fail("Target should have _UpbDefsWrappedCcInfo provider")
254        dep_ccinfos += [target[_UpbWrappedCcInfo].cc_info]
255    cc_info = _cc_library_func(
256        ctx = ctx,
257        name = ctx.rule.attr.name + "." + generator,
258        hdrs = files.hdrs,
259        srcs = files.srcs,
260        copts = ctx.attr._copts[_UpbProtoLibraryCopts].copts,
261        dep_ccinfos = dep_ccinfos,
262    )
263    return [cc_provider(cc_info = cc_info), file_provider(srcs = files)]
264
265def _upb_proto_library_aspect_impl(target, ctx):
266    return _upb_proto_aspect_impl(target, ctx, "upb", _UpbWrappedCcInfo, _WrappedGeneratedSrcsInfo)
267
268def _upb_proto_reflection_library_aspect_impl(target, ctx):
269    return _upb_proto_aspect_impl(target, ctx, "upbdefs", _UpbDefsWrappedCcInfo, _WrappedDefsGeneratedSrcsInfo)
270
271def _maybe_add(d):
272    if not _is_bazel:
273        d["_grep_includes"] = attr.label(
274            allow_single_file = True,
275            cfg = "host",
276            default = "//tools/cpp:grep-includes",
277        )
278    return d
279
280# upb_proto_library() ##########################################################
281
282_upb_proto_library_aspect = aspect(
283    attrs = _maybe_add({
284        "_copts": attr.label(
285            default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use",
286        ),
287        "_gen_upb": attr.label(
288            executable = True,
289            cfg = "host",
290            default = "//upbc:protoc-gen-upb",
291        ),
292        "_protoc": attr.label(
293            executable = True,
294            cfg = "host",
295            default = "@com_google_protobuf//:protoc",
296        ),
297        "_cc_toolchain": attr.label(
298            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
299        ),
300        "_upb": attr.label_list(default = [
301            "//:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
302            "//:upb",
303        ]),
304        "_fasttable_enabled": attr.label(default = "//:fasttable_enabled"),
305    }),
306    implementation = _upb_proto_library_aspect_impl,
307    provides = [
308        _UpbWrappedCcInfo,
309        _WrappedGeneratedSrcsInfo,
310    ],
311    attr_aspects = ["deps"],
312    fragments = ["cpp"],
313    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
314    incompatible_use_toolchain_transition = True,
315)
316
317upb_proto_library = rule(
318    output_to_genfiles = True,
319    implementation = _upb_proto_rule_impl,
320    attrs = {
321        "deps": attr.label_list(
322            aspects = [_upb_proto_library_aspect],
323            allow_rules = ["proto_library"],
324            providers = [ProtoInfo],
325        ),
326    },
327)
328
329# upb_proto_reflection_library() ###############################################
330
331_upb_proto_reflection_library_aspect = aspect(
332    attrs = _maybe_add({
333        "_copts": attr.label(
334            default = "//:upb_proto_library_copts__for_generated_code_only_do_not_use",
335        ),
336        "_gen_upbdefs": attr.label(
337            executable = True,
338            cfg = "host",
339            default = "//upbc:protoc-gen-upbdefs",
340        ),
341        "_protoc": attr.label(
342            executable = True,
343            cfg = "host",
344            default = "@com_google_protobuf//:protoc",
345        ),
346        "_cc_toolchain": attr.label(
347            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
348        ),
349        "_upbdefs": attr.label_list(
350            default = [
351                "//:generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
352                "//:upb",
353                "//:reflection",
354            ],
355        ),
356    }),
357    implementation = _upb_proto_reflection_library_aspect_impl,
358    provides = [
359        _UpbDefsWrappedCcInfo,
360        _WrappedDefsGeneratedSrcsInfo,
361    ],
362    required_aspect_providers = [
363        _UpbWrappedCcInfo,
364        _WrappedGeneratedSrcsInfo,
365    ],
366    attr_aspects = ["deps"],
367    fragments = ["cpp"],
368    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
369    incompatible_use_toolchain_transition = True,
370)
371
372upb_proto_reflection_library = rule(
373    output_to_genfiles = True,
374    implementation = _upb_proto_rule_impl,
375    attrs = {
376        "deps": attr.label_list(
377            aspects = [
378                _upb_proto_library_aspect,
379                _upb_proto_reflection_library_aspect,
380            ],
381            allow_rules = ["proto_library"],
382            providers = [ProtoInfo],
383        ),
384    },
385)
386