• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""This file implements rust_proto_library aspect."""
2
3load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
4
5# buildifier: disable=bzl-visibility
6load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVariantInfo")
7
8# buildifier: disable=bzl-visibility
9load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action")
10load("//bazel:upb_minitable_proto_library.bzl", "UpbMinitableCcInfo", "upb_minitable_proto_library_aspect")
11load("//bazel/common:proto_common.bzl", "proto_common")
12load("//bazel/common:proto_info.bzl", "ProtoInfo")
13
14visibility(["//rust/..."])
15
16CrateMappingInfo = provider(
17    doc = "Struct mapping crate name to the .proto import paths",
18    fields = {
19        "crate_name": "Crate name of the proto_library target",
20        "import_paths": "Import path used in .proto files of dependants to import the .proto " +
21                        "file of the current proto_library.",
22    },
23)
24RustProtoInfo = provider(
25    doc = "Rust protobuf provider info",
26    fields = {
27        "dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " +
28                            "transitive dependencies)",
29        "crate_mapping": "depset(CrateMappingInfo) containing mappings of all transitive " +
30                         "dependencies of the current proto_library.",
31    },
32)
33
34def label_to_crate_name(ctx, label, toolchain):
35    return label.name.replace("-", "_")
36
37def proto_rust_toolchain_label(is_upb):
38    if is_upb:
39        return "//rust:proto_rust_upb_toolchain"
40    else:
41        return "//rust:proto_rust_cpp_toolchain"
42
43def _register_crate_mapping_write_action(name, actions, crate_mappings):
44    """Registers an action that generates a crate mapping for a proto_library.
45
46    Args:
47        name: The name of the target being analyzed.
48        actions: The context's actions object.
49        crate_mappings: depset(CrateMappingInfo) to be rendered.
50            This sequence should already have duplicates removed.
51
52    Returns:
53        The generated `File` with the crate mapping.
54    """
55    mapping_file = actions.declare_file(
56        "{}.rust_crate_mapping".format(name),
57    )
58    content = actions.args()
59    content.set_param_file_format("multiline")
60    content.add_all(crate_mappings, map_each = _render_text_crate_mapping)
61    actions.write(content = content, output = mapping_file)
62
63    return mapping_file
64
65def _render_text_crate_mapping(mapping):
66    """Renders the mapping to an easily parseable file for a crate mapping.
67
68    Args:
69        mapping (CrateMappingInfo): A single crate mapping.
70
71    Returns:
72        A string containing the crate mapping for the target in simple format:
73            <crate_name>\n
74            <number of lines to follow>\n
75            <one import path per line>\n
76    """
77    crate_name = mapping.crate_name
78    import_paths = mapping.import_paths
79    return "\n".join(([crate_name, str(len(import_paths))] + list(import_paths)))
80
81def _generate_rust_gencode(
82        ctx,
83        proto_info,
84        proto_lang_toolchain,
85        crate_mapping,
86        is_upb):
87    """Generates Rust gencode
88
89        This function uses proto_common APIs and a ProtoLangToolchain to register an action
90        that invokes protoc with the right flags.
91    Args:
92        ctx (RuleContext): current rule context
93        proto_info (ProtoInfo): ProtoInfo of the proto_library target for which we are generating
94                    gencode
95        proto_lang_toolchain (ProtoLangToolchainInfo): proto lang toolchain for Rust
96        crate_mapping (File): File containing the mapping from .proto file import path to its
97                      corresponding containing Rust crate name.
98        is_upb (Bool): True when generating gencode for UPB, False otherwise.
99    Returns:
100        rs_outputs (([File], [File]): tuple(generated Rust files, generated C++ thunks).
101    """
102    actions = ctx.actions
103    rs_outputs = proto_common.declare_generated_files(
104        actions = actions,
105        proto_info = proto_info,
106        extension = ".{}.pb.rs".format("u" if is_upb else "c"),
107    )
108    if is_upb:
109        cc_outputs = []
110    else:
111        cc_outputs = proto_common.declare_generated_files(
112            actions = actions,
113            proto_info = proto_info,
114            extension = ".pb.thunks.cc",
115        )
116    additional_args = ctx.actions.args()
117
118    additional_args.add(
119        "--rust_opt=experimental-codegen=enabled,kernel={},bazel_crate_mapping={}".format(
120            "upb" if is_upb else "cpp",
121            crate_mapping.path,
122        ),
123    )
124
125    proto_common.compile(
126        actions = ctx.actions,
127        proto_info = proto_info,
128        additional_inputs = depset(direct = [crate_mapping]),
129        additional_args = additional_args,
130        generated_files = rs_outputs + cc_outputs,
131        proto_lang_toolchain_info = proto_lang_toolchain,
132        plugin_output = ctx.bin_dir.path,
133    )
134    return (rs_outputs, cc_outputs)
135
136def _get_crate_info(providers):
137    for provider in providers:
138        if hasattr(provider, "name"):
139            return provider
140    fail("Couldn't find a CrateInfo in the list of providers")
141
142def _get_dep_info(providers):
143    for provider in providers:
144        if hasattr(provider, "direct_crates"):
145            return provider
146    fail("Couldn't find a DepInfo in the list of providers")
147
148def _get_cc_info(providers):
149    for provider in providers:
150        if hasattr(provider, "linking_context"):
151            return provider
152    fail("Couldn't find a CcInfo in the list of providers")
153
154def _compile_cc(
155        ctx,
156        attr,
157        cc_toolchain,
158        feature_configuration,
159        src,
160        cc_infos):
161    """Compiles a C++ source file.
162
163    Args:
164      ctx: The rule context.
165      attr: The current rule's attributes.
166      cc_toolchain: A cc_toolchain.
167      feature_configuration: A feature configuration.
168      src: The source file to be compiled.
169      cc_infos: List[CcInfo]: A list of CcInfo dependencies.
170
171    Returns:
172      A CcInfo provider.
173    """
174    cc_info = cc_common.merge_cc_infos(direct_cc_infos = cc_infos)
175
176    (compilation_context, compilation_outputs) = cc_common.compile(
177        name = src.basename,
178        actions = ctx.actions,
179        feature_configuration = feature_configuration,
180        cc_toolchain = cc_toolchain,
181        srcs = [src],
182        user_compile_flags = attr.copts if hasattr(attr, "copts") else [],
183        compilation_contexts = [cc_info.compilation_context],
184    )
185
186    (linking_context, _) = cc_common.create_linking_context_from_compilation_outputs(
187        name = src.basename,
188        actions = ctx.actions,
189        feature_configuration = feature_configuration,
190        cc_toolchain = cc_toolchain,
191        compilation_outputs = compilation_outputs,
192        linking_contexts = [cc_info.linking_context],
193    )
194
195    return CcInfo(
196        compilation_context = compilation_context,
197        linking_context = linking_context,
198    )
199
200def _compile_rust(ctx, attr, src, extra_srcs, deps):
201    """Compiles a Rust source file.
202
203    Eventually this function could be upstreamed into rules_rust and be made present in rust_common.
204
205    Args:
206      ctx (RuleContext): The rule context.
207      attr (Attrs): The current rule's attributes (`ctx.attr` for rules, `ctx.rule.attr` for aspects)
208      src (File): The crate root source file to be compiled.
209      extra_srcs ([File]): Additional source files to include in the crate.
210      deps (List[DepVariantInfo]): A list of dependencies needed.
211
212    Returns:
213      A DepVariantInfo provider.
214    """
215    toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
216    output_hash = repr(hash(src.path))
217
218    crate_name = label_to_crate_name(ctx, ctx.label, toolchain)
219
220    lib_name = "{prefix}{name}-{lib_hash}{extension}".format(
221        prefix = "lib",
222        name = crate_name,
223        lib_hash = output_hash,
224        extension = ".rlib",
225    )
226
227    rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
228        prefix = "lib",
229        name = crate_name,
230        lib_hash = output_hash,
231        extension = ".rmeta",
232    )
233
234    lib = ctx.actions.declare_file(lib_name)
235    rmeta = ctx.actions.declare_file(rmeta_name)
236
237    # TODO: Use higher level rules_rust API once available.
238    providers = rustc_compile_action(
239        ctx = ctx,
240        attr = attr,
241        toolchain = toolchain,
242        crate_info_dict = dict(
243            name = crate_name,
244            type = "rlib",
245            root = src,
246            srcs = depset([src] + extra_srcs),
247            deps = depset(deps),
248            proc_macro_deps = depset([]),
249            aliases = {},
250            output = lib,
251            metadata = rmeta,
252            edition = "2021",
253            is_test = False,
254            rustc_env = {},
255            compile_data = depset([]),
256            compile_data_targets = depset([]),
257            owner = ctx.label,
258        ),
259        # Needed to make transitive public imports not violate layering.
260        force_all_deps_direct = True,
261        output_hash = output_hash,
262    )
263
264    return DepVariantInfo(
265        crate_info = _get_crate_info(providers),
266        dep_info = _get_dep_info(providers),
267        cc_info = _get_cc_info(providers),
268        build_info = None,
269    )
270
271def _rust_upb_proto_aspect_impl(target, ctx):
272    """Implements the Rust protobuf aspect logic for UPB kernel."""
273    return _rust_proto_aspect_common(target, ctx, is_upb = True)
274
275def _rust_cc_proto_aspect_impl(target, ctx):
276    """Implements the Rust protobuf aspect logic for C++ kernel."""
277    return _rust_proto_aspect_common(target, ctx, is_upb = False)
278
279def get_import_path(f):
280    if hasattr(proto_common, "get_import_path"):
281        return proto_common.get_import_path(f)
282    else:
283        return f.path
284
285def _rust_proto_aspect_common(target, ctx, is_upb):
286    if RustProtoInfo in target:
287        return []
288
289    proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo]
290    cc_toolchain = find_cpp_toolchain(ctx)
291    toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
292
293    feature_configuration = cc_common.configure_features(
294        ctx = ctx,
295        cc_toolchain = cc_toolchain,
296        requested_features = ctx.features,
297        unsupported_features = ctx.disabled_features,
298    )
299
300    proto_srcs = getattr(ctx.rule.files, "srcs", [])
301    proto_deps = getattr(ctx.rule.attr, "deps", [])
302    transitive_crate_mappings = []
303    for dep in proto_deps:
304        rust_proto_info = dep[RustProtoInfo]
305        transitive_crate_mappings.append(rust_proto_info.crate_mapping)
306
307    mapping_for_current_target = depset(transitive = transitive_crate_mappings)
308    crate_mapping_file = _register_crate_mapping_write_action(
309        target.label.name,
310        ctx.actions,
311        mapping_for_current_target,
312    )
313
314    (gencode, thunks) = _generate_rust_gencode(
315        ctx,
316        target[ProtoInfo],
317        proto_lang_toolchain,
318        crate_mapping_file,
319        is_upb,
320    )
321
322    if is_upb:
323        thunks_cc_info = target[UpbMinitableCcInfo].cc_info
324    else:
325        dep_cc_infos = []
326        for dep in proto_deps:
327            dep_cc_infos.append(dep[CcInfo])
328
329        thunks_cc_info = cc_common.merge_cc_infos(cc_infos = [_compile_cc(
330            feature_configuration = feature_configuration,
331            src = thunk,
332            ctx = ctx,
333            attr = attr,
334            cc_toolchain = cc_toolchain,
335            cc_infos = [target[CcInfo]] + [dep[CcInfo] for dep in ctx.attr._cpp_thunks_deps] + dep_cc_infos,
336        ) for thunk in thunks])
337
338    runtime = proto_lang_toolchain.runtime
339    dep_variant_info_for_runtime = DepVariantInfo(
340        crate_info = runtime[CrateInfo] if CrateInfo in runtime else None,
341        dep_info = runtime[DepInfo] if DepInfo in runtime else None,
342        cc_info = runtime[CcInfo] if CcInfo in runtime else None,
343        build_info = None,
344    )
345    dep_variant_info_for_native_gencode = DepVariantInfo(cc_info = thunks_cc_info)
346
347    dep_variant_info = _compile_rust(
348        ctx = ctx,
349        attr = ctx.rule.attr,
350        src = gencode[0],
351        extra_srcs = gencode[1:],
352        deps = [dep_variant_info_for_runtime, dep_variant_info_for_native_gencode] + (
353            [d[RustProtoInfo].dep_variant_info for d in proto_deps]
354        ),
355    )
356    return [RustProtoInfo(
357        dep_variant_info = dep_variant_info,
358        crate_mapping = depset(
359            direct = [CrateMappingInfo(
360                crate_name = label_to_crate_name(ctx, target.label, toolchain),
361                import_paths = tuple([get_import_path(f) for f in proto_srcs]),
362            )],
363            transitive = transitive_crate_mappings,
364        ),
365    )]
366
367def _make_proto_library_aspect(is_upb):
368    return aspect(
369        implementation = (_rust_upb_proto_aspect_impl if is_upb else _rust_cc_proto_aspect_impl),
370        attr_aspects = ["deps"],
371        requires = ([upb_minitable_proto_library_aspect] if is_upb else [cc_proto_aspect]),
372        attrs = {
373            "_cc_toolchain": attr.label(
374                doc = (
375                    "In order to use find_cc_toolchain, your rule has to depend " +
376                    "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
377                    "docs for details."
378                ),
379                default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
380            ),
381            "_collect_cc_coverage": attr.label(
382                default = Label("@rules_rust//util:collect_coverage"),
383                executable = True,
384                cfg = "exec",
385            ),
386            "_cpp_thunks_deps": attr.label_list(
387                default = [
388                    Label("//rust/cpp_kernel:cpp_api"),
389                    Label("//src/google/protobuf"),
390                    Label("//src/google/protobuf:protobuf_lite"),
391                ],
392            ),
393            "_error_format": attr.label(
394                default = Label("@rules_rust//:error_format"),
395            ),
396            "_extra_exec_rustc_flag": attr.label(
397                default = Label("@rules_rust//:extra_exec_rustc_flag"),
398            ),
399            "_extra_exec_rustc_flags": attr.label(
400                default = Label("@rules_rust//:extra_exec_rustc_flags"),
401            ),
402            "_extra_rustc_flag": attr.label(
403                default = Label("@rules_rust//:extra_rustc_flag"),
404            ),
405            "_extra_rustc_flags": attr.label(
406                default = Label("@rules_rust//:extra_rustc_flags"),
407            ),
408            "_process_wrapper": attr.label(
409                doc = "A process wrapper for running rustc on all platforms.",
410                default = Label("@rules_rust//util/process_wrapper"),
411                executable = True,
412                allow_single_file = True,
413                cfg = "exec",
414            ),
415            "_proto_lang_toolchain": attr.label(
416                default = Label(proto_rust_toolchain_label(is_upb)),
417            ),
418        },
419        fragments = ["cpp"],
420        toolchains = [
421            "@rules_rust//rust:toolchain_type",
422            "@bazel_tools//tools/cpp:toolchain_type",
423        ],
424    )
425
426rust_upb_proto_library_aspect = _make_proto_library_aspect(is_upb = True)
427rust_cc_proto_library_aspect = _make_proto_library_aspect(is_upb = False)
428