diff --git a/rust/bazel/aspects.bzl b/rust/bazel/aspects.bzl index 3ee6a283a047c..a13ab7a4918e6 100644 --- a/rust/bazel/aspects.bzl +++ b/rust/bazel/aspects.bzl @@ -27,8 +27,10 @@ CrateMappingInfo = provider( RustProtoInfo = provider( doc = "Rust protobuf provider info", fields = { - "dep_variant_infos": "List of DepVariantInfo for the compiled Rust " + - "gencode (also covers its transitive dependencies)", + "dep_variant_infos": "List of DepVariantInfo for the compiled Rust gencode", + "exports_dep_variant_infos": "List(DepVariantInfo): Transitive infos from targets from the proto_library.exports attribute. \ + This is a list instead of a depset, since we pass them as direct dependencies when compiling rust code. \ + We assume the proto exports feature is not widely used in a way where this will lead to unacceptable analysis-time overhead.", "crate_mapping": "depset(CrateMappingInfo) containing mappings of all transitive " + "dependencies of the current proto_library.", }, @@ -43,6 +45,9 @@ def _rust_version_ge(version): return _version_parts(RUST_VERSION) >= _version_parts(version) def label_to_crate_name(ctx, label, toolchain): + return compute_crate_name(ctx.workspace_name, label, toolchain) + +def encode_label_as_crate_name(label): return label.name.replace("-", "_") def proto_rust_toolchain_label(is_upb): @@ -219,7 +224,7 @@ def _compile_cc( linking_context = linking_context, ) -def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime): +def _compile_rust(ctx, attr, src, extra_srcs, deps, aliases, runtime): """Compiles a Rust source file. Eventually this function could be upstreamed into rules_rust and be made present in rust_common. @@ -230,6 +235,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime): 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. + aliases (dict[Target, str]): A mapping from dependency target to its crate name. runtime: The protobuf runtime target. Returns: @@ -238,7 +244,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime): toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"] output_hash = repr(hash(src.path)) - crate_name = label_to_crate_name(ctx, ctx.label, toolchain) + crate_name = compute_crate_name(ctx, ctx.label, toolchain) lib_name = "{prefix}{name}-{lib_hash}{extension}".format( prefix = "lib", @@ -285,7 +291,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime): # generated code to use a consistent name, even though the actual # name of the runtime crate varies depending on the protobuf kernel # and build system. - aliases = {runtime: "protobuf"}, + aliases = {runtime: "protobuf"} | aliases, output = lib, metadata = rmeta, edition = "2024", @@ -296,7 +302,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime): owner = ctx.label, ), # Needed to make transitive public imports not violate layering. - force_all_deps_direct = True, + # force_all_deps_direct = True, output_hash = output_hash, ) @@ -339,12 +345,19 @@ def _rust_proto_aspect_common(target, ctx, is_upb): for info in [d[RustProtoInfo].dep_variant_infos for d in proto_deps]: dep_variant_infos += info + exported_proto_deps = getattr(ctx.rule.attr, "exports", []) + exports_dep_variant_infos = [] + for d in exported_proto_deps: + exports_dep_variant_infos.extend(d[RustProtoInfo].dep_variant_infos) + exports_dep_variant_infos.extend(d[RustProtoInfo].exports_dep_variant_infos) + # If there are no srcs, then this is an alias library (which in Rust acts as a middle # library in a dependency chain). Don't generate any Rust code for it, but do propagate the # crate mappings. if not proto_srcs: return [RustProtoInfo( dep_variant_infos = dep_variant_infos, + exports_dep_variant_infos = exports_dep_variant_infos, crate_mapping = depset(transitive = transitive_crate_mappings), )] @@ -415,19 +428,37 @@ def _rust_proto_aspect_common(target, ctx, is_upb): for dep in ctx.attr._extra_deps ] + proto_deps_for_aliases = dep_variant_infos + exports_dep_variant_infos + + aliases = {} + + for d in proto_deps_for_aliases: + label = Label(d.crate_info.owner) + target = struct(label = label) + qualified_name = encode_label_as_crate_name(label) + aliases[target] = qualified_name + + deps = ([dep_variant_info_for_runtime] + + dep_variant_info_for_native_gencode + + dep_variant_infos + + extra_dep_variant_infos + + exports_dep_variant_infos) + dep_variant_info = _compile_rust( ctx = ctx, attr = ctx.rule.attr, src = entry_point_rs_output, extra_srcs = rs_gencode, - deps = [dep_variant_info_for_runtime] + dep_variant_info_for_native_gencode + dep_variant_infos + extra_dep_variant_infos, + deps = deps, + aliases = aliases, runtime = runtime, ) return [RustProtoInfo( dep_variant_infos = [dep_variant_info], + exports_dep_variant_infos = exports_dep_variant_infos, crate_mapping = depset( direct = [CrateMappingInfo( - crate_name = label_to_crate_name(ctx, target.label, toolchain), + crate_name = encode_label_as_crate_name(ctx.label), import_paths = tuple([get_import_path(f) for f in proto_srcs]), )], transitive = transitive_crate_mappings, @@ -437,7 +468,7 @@ def _rust_proto_aspect_common(target, ctx, is_upb): 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"], + attr_aspects = ["deps", "exports"], requires = ([] if is_upb else [cc_proto_aspect]), attrs = { "_collect_cc_coverage": attr.label(