• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Implementation of the aspect that powers the upb_*_proto_library() rules."""
2
3load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
4load("//bazel/common:proto_common.bzl", "proto_common")
5load("//bazel/common:proto_info.bzl", "ProtoInfo")
6load(":upb_proto_library_internal/cc_library_func.bzl", "cc_library_func")
7load(":upb_proto_library_internal/copts.bzl", "UpbProtoLibraryCoptsInfo")
8
9_is_google3 = False
10
11GeneratedSrcsInfo = provider(
12    "Provides generated headers and sources",
13    fields = {
14        "srcs": "list of srcs",
15        "hdrs": "list of hdrs",
16    },
17)
18
19def output_dir(ctx, proto_info):
20    """Returns the output directory where generated proto files will be placed.
21
22    Args:
23      ctx: Rule context.
24      proto_info: ProtoInfo provider.
25
26    Returns:
27      A string specifying the output directory
28    """
29    proto_root = proto_info.proto_source_root
30    if proto_root.startswith(ctx.bin_dir.path):
31        path = proto_root
32    else:
33        path = ctx.bin_dir.path + "/" + proto_root
34
35    if proto_root == ".":
36        path = ctx.bin_dir.path
37    return path
38
39def _concat_lists(lists):
40    ret = []
41    for lst in lists:
42        ret = ret + lst
43    return ret
44
45def _merge_generated_srcs(srcs):
46    return GeneratedSrcsInfo(
47        srcs = _concat_lists([s.srcs for s in srcs]),
48        hdrs = _concat_lists([s.hdrs for s in srcs]),
49    )
50
51def _get_implicit_weak_field_sources(ctx, proto_info):
52    # Creating one .cc file for each Message in a proto allows the linker to be more aggressive
53    # about removing unused classes. However, since the number of outputs won't be known at Blaze
54    # analysis time, all of the generated source files are put in a directory and a TreeArtifact is
55    # used to represent them.
56    proto_artifacts = []
57    for proto_source in proto_info.direct_sources:
58        # We can have slashes in the target name. For example, proto_source can be:
59        # dir/a.proto. However proto_source.basename will return a.proto, when in reality
60        # the BUILD file declares it as dir/a.proto, because target name contains a slash.
61        # There is no good workaround for this.
62        # I am using ctx.label.package to check if the name of the target contains slash or not.
63        # This is similar to what declare_directory does.
64        if not proto_source.short_path.startswith(ctx.label.package):
65            fail("This should never happen, proto source {} path does not start with {}.".format(
66                proto_source.short_path,
67                ctx.label.package,
68            ))
69        proto_source_name = proto_source.short_path[len(ctx.label.package) + 1:]
70        last_dot = proto_source_name.rfind(".")
71        if last_dot != -1:
72            proto_source_name = proto_source_name[:last_dot]
73        proto_artifacts.append(ctx.actions.declare_directory(proto_source_name + ".upb_weak_minitables"))
74
75    return proto_artifacts
76
77def _get_feature_configuration(ctx, cc_toolchain, proto_info):
78    requested_features = list(ctx.features)
79
80    # Disable the whole-archive behavior for protobuf generated code when the
81    # proto_one_output_per_message feature is enabled.
82    requested_features.append("disable_whole_archive_for_static_lib_if_proto_one_output_per_message")
83    unsupported_features = list(ctx.disabled_features)
84    if len(proto_info.direct_sources) != 0:
85        requested_features.append("header_modules")
86    else:
87        unsupported_features.append("header_modules")
88    return cc_common.configure_features(
89        ctx = ctx,
90        cc_toolchain = cc_toolchain,
91        requested_features = requested_features,
92        unsupported_features = unsupported_features,
93    )
94
95def _generate_srcs_list(ctx, generator, proto_info):
96    if len(proto_info.direct_sources) == 0:
97        return GeneratedSrcsInfo(srcs = [], hdrs = [], includes = [])
98
99    ext = "." + generator
100    srcs = []
101    hdrs = proto_common.declare_generated_files(
102        ctx.actions,
103        extension = ext + ".h",
104        proto_info = proto_info,
105    )
106    if not (generator == "upb" and _is_google3):
107        # TODO: The OSS build should also exclude this file for the upb generator,
108        # as it is empty and unnecessary.  We only added it to make the OSS build happy on
109        # Windows and macOS.
110        srcs += proto_common.declare_generated_files(
111            ctx.actions,
112            extension = ext + ".c",
113            proto_info = proto_info,
114        )
115
116    return GeneratedSrcsInfo(
117        srcs = srcs,
118        hdrs = hdrs,
119    )
120
121def _generate_upb_protos(ctx, generator, proto_info, feature_configuration):
122    implicit_weak = generator == "upb_minitable" and cc_common.is_enabled(
123        feature_configuration = feature_configuration,
124        feature_name = "proto_one_output_per_message",
125    )
126
127    srcs = _generate_srcs_list(ctx, generator, proto_info)
128    additional_args = ctx.actions.args()
129
130    if implicit_weak:
131        srcs.srcs.extend(_get_implicit_weak_field_sources(ctx, proto_info))
132        additional_args.add("--upb_minitable_opt=one_output_per_message")
133
134    proto_common.compile(
135        actions = ctx.actions,
136        proto_info = proto_info,
137        proto_lang_toolchain_info = _get_lang_toolchain(ctx, generator),
138        generated_files = srcs.srcs + srcs.hdrs,
139        experimental_exec_group = "proto_compiler",
140        additional_args = additional_args,
141    )
142
143    return srcs
144
145def _generate_name(ctx, generator):
146    return ctx.rule.attr.name + "." + generator
147
148def _get_dep_cc_infos(target, ctx, generator, cc_provider, dep_cc_provider):
149    rule_deps = ctx.rule.attr.deps
150    dep_ccinfos = [dep[cc_provider].cc_info for dep in rule_deps]
151    if dep_cc_provider:
152        # This gives access to our direct sibling.  eg. foo.upb.h can #include "foo.upb_minitable.h"
153        dep_ccinfos.append(target[dep_cc_provider].cc_info)
154
155        # This gives access to imports.  eg. foo.upb.h can #include "import1.upb_minitable.h"
156        # But not transitive imports, eg. foo.upb.h cannot #include "transitive_import1.upb_minitable.h"
157        dep_ccinfos += [dep[dep_cc_provider].cc_info for dep in rule_deps]
158
159    return dep_ccinfos
160
161def _get_lang_toolchain(ctx, generator):
162    lang_toolchain_name = "_" + generator + "_toolchain"
163    return getattr(ctx.attr, lang_toolchain_name)[proto_common.ProtoLangToolchainInfo]
164
165def _compile_upb_protos(ctx, files, generator, dep_ccinfos, cc_provider, proto_info):
166    cc_info = cc_library_func(
167        ctx = ctx,
168        name = _generate_name(ctx, generator),
169        hdrs = files.hdrs,
170        srcs = files.srcs,
171        includes = [output_dir(ctx, proto_info)],
172        copts = ctx.attr._copts[UpbProtoLibraryCoptsInfo].copts,
173        dep_ccinfos = dep_ccinfos,
174    )
175
176    return cc_provider(
177        cc_info = cc_info,
178    )
179
180_GENERATORS = ["upb", "upbdefs", "upb_minitable"]
181
182def _get_hint_providers(ctx, generator):
183    if generator not in _GENERATORS:
184        fail("Please add new generator '{}' to _GENERATORS list".format(generator))
185
186    possible_owners = []
187    for generator in _GENERATORS:
188        possible_owners.append(ctx.label.relative(_generate_name(ctx, generator)))
189
190    if hasattr(cc_common, "CcSharedLibraryHintInfo"):
191        return [cc_common.CcSharedLibraryHintInfo(owners = possible_owners)]
192    elif hasattr(cc_common, "CcSharedLibraryHintInfo_6_X_constructor_do_not_use"):
193        # This branch can be deleted once 6.X is not supported by upb rules
194        return [cc_common.CcSharedLibraryHintInfo_6_X_constructor_do_not_use(owners = possible_owners)]
195
196    return []
197
198def upb_proto_aspect_impl(
199        target,
200        ctx,
201        generator,
202        cc_provider,
203        dep_cc_provider,
204        file_provider,
205        provide_cc_shared_library_hints = True):
206    """A shared aspect implementation for upb_*proto_library() rules.
207
208    Args:
209      target: The `target` parameter from the aspect function.
210      ctx: The `ctx` parameter from the aspect function.
211      generator: A string describing which aspect we are generating. This triggers several special
212        behaviors, and ideally this will be refactored to be less magical.
213      cc_provider: The provider that this aspect will attach to the target. Should contain a
214        `cc_info` field. The aspect will ensure that each compilation action can compile and link
215        against this provider's cc_info for all proto_library() deps.
216      dep_cc_provider: For aspects that depend on other aspects, this is the provider of the aspect
217        that we depend on. The aspect will be able to include the header files from this provider.
218      file_provider: A provider that this aspect will attach to the target to expose the source
219        files generated by this aspect. These files are primarily useful for returning in
220        DefaultInfo(), so users who build the upb_*proto_library() rule directly can view the
221        generated sources.
222      provide_cc_shared_library_hints: Whether shared library hints should be provided.
223
224    Returns:
225      The `cc_provider` and `file_provider` providers as described above.
226    """
227    dep_ccinfos = _get_dep_cc_infos(target, ctx, generator, cc_provider, dep_cc_provider)
228    if not getattr(ctx.rule.attr, "srcs", []):
229        # This target doesn't declare any sources, reexport all its deps instead.
230        # This is known as an "alias library":
231        #    https://bazel.build/reference/be/protocol-buffer#proto_library.srcs
232        files = _merge_generated_srcs([dep[file_provider].srcs for dep in ctx.rule.attr.deps])
233        wrapped_cc_info = cc_provider(
234            cc_info = cc_common.merge_cc_infos(direct_cc_infos = dep_ccinfos),
235        )
236    else:
237        proto_info = target[ProtoInfo]
238        cc_toolchain = find_cpp_toolchain(ctx)
239        feature_configuration = _get_feature_configuration(ctx, cc_toolchain, proto_info)
240        files = _generate_upb_protos(
241            ctx,
242            generator,
243            proto_info,
244            feature_configuration,
245        )
246        wrapped_cc_info = _compile_upb_protos(
247            ctx,
248            files,
249            generator,
250            dep_ccinfos + [_get_lang_toolchain(ctx, generator).runtime[CcInfo]],
251            cc_provider,
252            proto_info,
253        )
254
255    hints = _get_hint_providers(ctx, generator) if provide_cc_shared_library_hints else []
256
257    return hints + [
258        file_provider(srcs = files),
259        wrapped_cc_info,
260    ]
261