| """This file implements an experimental, do-not-use-kind of rust_proto_library. |
| |
| Disclaimer: This project is experimental, under heavy development, and should not |
| be used yet.""" |
| |
| load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") |
| load("@rules_proto//proto:defs.bzl", "ProtoInfo", "proto_common") |
| |
| # buildifier: disable=bzl-visibility |
| load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVariantInfo") |
| |
| # buildifier: disable=bzl-visibility |
| load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action") |
| load("//bazel:upb_proto_library.bzl", "UpbWrappedCcInfo", "upb_proto_library_aspect") |
| |
| visibility(["//rust/..."]) |
| |
| CrateMappingInfo = provider( |
| doc = "Struct mapping crate name to the .proto import paths", |
| fields = { |
| "crate_name": "Crate name of the proto_library target", |
| "import_paths": "Import path used in .proto files of dependants to import the .proto " + |
| "file of the current proto_library.", |
| }, |
| ) |
| RustProtoInfo = provider( |
| doc = "Rust protobuf provider info", |
| fields = { |
| "dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " + |
| "transitive dependencies)", |
| "crate_mapping": "depset(CrateMappingInfo) containing mappings of all transitive " + |
| "dependencies of the current proto_library.", |
| }, |
| ) |
| |
| def label_to_crate_name(ctx, label, toolchain): |
| return str(label).rsplit(":", 1)[1].replace("-", "_") |
| |
| def proto_rust_toolchain_label(is_upb): |
| if is_upb: |
| return "//rust:proto_rust_upb_toolchain" |
| else: |
| return "//rust:proto_rust_cpp_toolchain" |
| |
| def _register_crate_mapping_write_action(name, actions, crate_mappings): |
| """Registers an action that generates a crate mapping for a proto_library. |
| |
| Args: |
| name: The name of the target being analyzed. |
| actions: The context's actions object. |
| crate_mappings: depset(CrateMappingInfo) to be rendered. |
| This sequence should already have duplicates removed. |
| |
| Returns: |
| The generated `File` with the crate mapping. |
| """ |
| mapping_file = actions.declare_file( |
| "{}.rust_crate_mapping".format(name), |
| ) |
| content = actions.args() |
| content.set_param_file_format("multiline") |
| content.add_all(crate_mappings, map_each = _render_text_crate_mapping) |
| actions.write(content = content, output = mapping_file) |
| |
| return mapping_file |
| |
| def _render_text_crate_mapping(mapping): |
| """Renders the mapping to an easily parseable file for a crate mapping. |
| |
| Args: |
| mapping (CrateMappingInfo): A single crate mapping. |
| |
| Returns: |
| A string containing the crate mapping for the target in simple format: |
| <crate_name>\n |
| <number of lines to follow>\n |
| <one import path per line>\n |
| """ |
| crate_name = mapping.crate_name |
| import_paths = mapping.import_paths |
| return "\n".join(([crate_name, str(len(import_paths))] + list(import_paths))) |
| |
| def _generate_rust_gencode( |
| ctx, |
| proto_info, |
| proto_lang_toolchain, |
| crate_mapping, |
| is_upb): |
| """Generates Rust gencode |
| |
| This function uses proto_common APIs and a ProtoLangToolchain to register an action |
| that invokes protoc with the right flags. |
| Args: |
| ctx (RuleContext): current rule context |
| proto_info (ProtoInfo): ProtoInfo of the proto_library target for which we are generating |
| gencode |
| proto_lang_toolchain (ProtoLangToolchainInfo): proto lang toolchain for Rust |
| crate_mapping (File): File containing the mapping from .proto file import path to its |
| corresponding containing Rust crate name. |
| is_upb (Bool): True when generating gencode for UPB, False otherwise. |
| Returns: |
| rs_outputs (([File], [File]): tuple(generated Rust files, generated C++ thunks). |
| """ |
| actions = ctx.actions |
| rs_outputs = proto_common.declare_generated_files( |
| actions = actions, |
| proto_info = proto_info, |
| extension = ".{}.pb.rs".format("u" if is_upb else "c"), |
| ) |
| if is_upb: |
| cc_outputs = [] |
| else: |
| cc_outputs = proto_common.declare_generated_files( |
| actions = actions, |
| proto_info = proto_info, |
| extension = ".pb.thunks.cc", |
| ) |
| additional_args = ctx.actions.args() |
| |
| additional_args.add( |
| "--rust_opt=experimental-codegen=enabled,kernel={},bazel_crate_mapping={}".format( |
| "upb" if is_upb else "cpp", |
| crate_mapping.path, |
| ), |
| ) |
| |
| proto_common.compile( |
| actions = ctx.actions, |
| proto_info = proto_info, |
| additional_inputs = depset(direct = [crate_mapping]), |
| additional_args = additional_args, |
| generated_files = rs_outputs + cc_outputs, |
| proto_lang_toolchain_info = proto_lang_toolchain, |
| plugin_output = ctx.bin_dir.path, |
| ) |
| return (rs_outputs, cc_outputs) |
| |
| def _get_crate_info(providers): |
| for provider in providers: |
| if hasattr(provider, "name"): |
| return provider |
| fail("Couldn't find a CrateInfo in the list of providers") |
| |
| def _get_dep_info(providers): |
| for provider in providers: |
| if hasattr(provider, "direct_crates"): |
| return provider |
| fail("Couldn't find a DepInfo in the list of providers") |
| |
| def _get_cc_info(providers): |
| for provider in providers: |
| if hasattr(provider, "linking_context"): |
| return provider |
| fail("Couldn't find a CcInfo in the list of providers") |
| |
| def _compile_cc( |
| ctx, |
| attr, |
| cc_toolchain, |
| feature_configuration, |
| src, |
| cc_infos): |
| """Compiles a C++ source file. |
| |
| Args: |
| ctx: The rule context. |
| attr: The current rule's attributes. |
| cc_toolchain: A cc_toolchain. |
| feature_configuration: A feature configuration. |
| src: The source file to be compiled. |
| cc_infos: List[CcInfo]: A list of CcInfo dependencies. |
| |
| Returns: |
| A CcInfo provider. |
| """ |
| cc_info = cc_common.merge_cc_infos(direct_cc_infos = cc_infos) |
| |
| (compilation_context, compilation_outputs) = cc_common.compile( |
| name = src.basename, |
| actions = ctx.actions, |
| feature_configuration = feature_configuration, |
| cc_toolchain = cc_toolchain, |
| srcs = [src], |
| user_compile_flags = attr.copts if hasattr(attr, "copts") else [], |
| compilation_contexts = [cc_info.compilation_context], |
| ) |
| |
| (linking_context, _) = cc_common.create_linking_context_from_compilation_outputs( |
| name = src.basename, |
| actions = ctx.actions, |
| feature_configuration = feature_configuration, |
| cc_toolchain = cc_toolchain, |
| compilation_outputs = compilation_outputs, |
| linking_contexts = [cc_info.linking_context], |
| ) |
| |
| return CcInfo( |
| compilation_context = compilation_context, |
| linking_context = linking_context, |
| ) |
| |
| def _compile_rust(ctx, attr, src, extra_srcs, deps): |
| """Compiles a Rust source file. |
| |
| Eventually this function could be upstreamed into rules_rust and be made present in rust_common. |
| |
| Args: |
| ctx (RuleContext): The rule context. |
| attr (Attrs): The current rule's attributes (`ctx.attr` for rules, `ctx.rule.attr` for aspects) |
| src (File): The crate root source file to be compiled. |
| extra_srcs ([File]): Additional source files to include in the crate. |
| deps (List[DepVariantInfo]): A list of dependencies needed. |
| |
| Returns: |
| A DepVariantInfo provider. |
| """ |
| toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"] |
| output_hash = repr(hash(src.path)) |
| |
| crate_name = label_to_crate_name(ctx, ctx.label, toolchain) |
| |
| lib_name = "{prefix}{name}-{lib_hash}{extension}".format( |
| prefix = "lib", |
| name = crate_name, |
| lib_hash = output_hash, |
| extension = ".rlib", |
| ) |
| |
| rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format( |
| prefix = "lib", |
| name = crate_name, |
| lib_hash = output_hash, |
| extension = ".rmeta", |
| ) |
| |
| lib = ctx.actions.declare_file(lib_name) |
| rmeta = ctx.actions.declare_file(rmeta_name) |
| |
| # TODO: Use higher level rules_rust API once available. |
| providers = rustc_compile_action( |
| ctx = ctx, |
| attr = attr, |
| toolchain = toolchain, |
| crate_info_dict = dict( |
| name = crate_name, |
| type = "rlib", |
| root = src, |
| srcs = depset([src] + extra_srcs), |
| deps = depset(deps), |
| proc_macro_deps = depset([]), |
| aliases = {}, |
| output = lib, |
| metadata = rmeta, |
| edition = "2021", |
| is_test = False, |
| rustc_env = {}, |
| compile_data = depset([]), |
| compile_data_targets = depset([]), |
| owner = ctx.label, |
| ), |
| # Needed to make transitive public imports not violate layering. |
| force_all_deps_direct = True, |
| output_hash = output_hash, |
| ) |
| |
| return DepVariantInfo( |
| crate_info = _get_crate_info(providers), |
| dep_info = _get_dep_info(providers), |
| cc_info = _get_cc_info(providers), |
| build_info = None, |
| ) |
| |
| def _rust_upb_proto_aspect_impl(target, ctx): |
| """Implements the Rust protobuf aspect logic for UPB kernel.""" |
| return _rust_proto_aspect_common(target, ctx, is_upb = True) |
| |
| def _rust_cc_proto_aspect_impl(target, ctx): |
| """Implements the Rust protobuf aspect logic for C++ kernel.""" |
| return _rust_proto_aspect_common(target, ctx, is_upb = False) |
| |
| def get_import_path(f): |
| if hasattr(proto_common, "get_import_path"): |
| return proto_common.get_import_path(f) |
| else: |
| return f.path |
| |
| def _rust_proto_aspect_common(target, ctx, is_upb): |
| if RustProtoInfo in target: |
| return [] |
| |
| proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo] |
| cc_toolchain = find_cpp_toolchain(ctx) |
| toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"] |
| |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = ctx.disabled_features, |
| ) |
| |
| proto_srcs = getattr(ctx.rule.files, "srcs", []) |
| proto_deps = getattr(ctx.rule.attr, "deps", []) |
| transitive_crate_mappings = [] |
| for dep in proto_deps: |
| rust_proto_info = dep[RustProtoInfo] |
| transitive_crate_mappings.append(rust_proto_info.crate_mapping) |
| |
| mapping_for_current_target = depset(transitive = transitive_crate_mappings) |
| crate_mapping_file = _register_crate_mapping_write_action( |
| target.label.name, |
| ctx.actions, |
| mapping_for_current_target, |
| ) |
| |
| (gencode, thunks) = _generate_rust_gencode( |
| ctx, |
| target[ProtoInfo], |
| proto_lang_toolchain, |
| crate_mapping_file, |
| is_upb, |
| ) |
| |
| if is_upb: |
| thunks_cc_info = target[UpbWrappedCcInfo].cc_info_with_thunks |
| else: |
| dep_cc_infos = [] |
| for dep in proto_deps: |
| dep_cc_infos.append(dep[CcInfo]) |
| |
| thunks_cc_info = cc_common.merge_cc_infos(cc_infos = [_compile_cc( |
| feature_configuration = feature_configuration, |
| src = thunk, |
| ctx = ctx, |
| attr = attr, |
| cc_toolchain = cc_toolchain, |
| cc_infos = [target[CcInfo]] + [dep[CcInfo] for dep in ctx.attr._cpp_thunks_deps] + dep_cc_infos, |
| ) for thunk in thunks]) |
| |
| runtime = proto_lang_toolchain.runtime |
| dep_variant_info_for_runtime = DepVariantInfo( |
| crate_info = runtime[CrateInfo] if CrateInfo in runtime else None, |
| dep_info = runtime[DepInfo] if DepInfo in runtime else None, |
| cc_info = runtime[CcInfo] if CcInfo in runtime else None, |
| build_info = None, |
| ) |
| dep_variant_info_for_native_gencode = DepVariantInfo(cc_info = thunks_cc_info) |
| |
| dep_variant_info = _compile_rust( |
| ctx = ctx, |
| attr = ctx.rule.attr, |
| src = gencode[0], |
| extra_srcs = gencode[1:], |
| deps = [dep_variant_info_for_runtime, dep_variant_info_for_native_gencode] + ( |
| [d[RustProtoInfo].dep_variant_info for d in proto_deps] |
| ), |
| ) |
| return [RustProtoInfo( |
| dep_variant_info = dep_variant_info, |
| crate_mapping = depset( |
| direct = [CrateMappingInfo( |
| crate_name = label_to_crate_name(ctx, target.label, toolchain), |
| import_paths = tuple([get_import_path(f) for f in proto_srcs]), |
| )], |
| transitive = transitive_crate_mappings, |
| ), |
| )] |
| |
| def _make_proto_library_aspect(is_upb): |
| return aspect( |
| implementation = (_rust_upb_proto_aspect_impl if is_upb else _rust_cc_proto_aspect_impl), |
| attr_aspects = ["deps"], |
| requires = ([upb_proto_library_aspect] if is_upb else [cc_proto_aspect]), |
| attrs = { |
| "_cc_toolchain": attr.label( |
| doc = ( |
| "In order to use find_cc_toolchain, your rule has to depend " + |
| "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " + |
| "docs for details." |
| ), |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| "_collect_cc_coverage": attr.label( |
| default = Label("@rules_rust//util:collect_coverage"), |
| executable = True, |
| cfg = "exec", |
| ), |
| "_cpp_thunks_deps": attr.label_list( |
| default = [ |
| Label("//rust/cpp_kernel:cpp_api"), |
| Label("//src/google/protobuf"), |
| ], |
| ), |
| "_error_format": attr.label( |
| default = Label("@rules_rust//:error_format"), |
| ), |
| "_extra_exec_rustc_flag": attr.label( |
| default = Label("@rules_rust//:extra_exec_rustc_flag"), |
| ), |
| "_extra_exec_rustc_flags": attr.label( |
| default = Label("@rules_rust//:extra_exec_rustc_flags"), |
| ), |
| "_extra_rustc_flag": attr.label( |
| default = Label("@rules_rust//:extra_rustc_flag"), |
| ), |
| "_extra_rustc_flags": attr.label( |
| default = Label("@rules_rust//:extra_rustc_flags"), |
| ), |
| "_process_wrapper": attr.label( |
| doc = "A process wrapper for running rustc on all platforms.", |
| default = Label("@rules_rust//util/process_wrapper"), |
| executable = True, |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| "_proto_lang_toolchain": attr.label( |
| default = Label(proto_rust_toolchain_label(is_upb)), |
| ), |
| }, |
| fragments = ["cpp"], |
| toolchains = [ |
| "@rules_rust//rust:toolchain_type", |
| "@bazel_tools//tools/cpp:toolchain_type", |
| ], |
| ) |
| |
| rust_upb_proto_library_aspect = _make_proto_library_aspect(is_upb = True) |
| rust_cc_proto_library_aspect = _make_proto_library_aspect(is_upb = False) |