Skip to content

Commit de6221f

Browse files
krasimirggcopybara-github
authored andcommitted
Internal change
PiperOrigin-RevId: 808534743
1 parent fa88f32 commit de6221f

25 files changed

+1040
-20
lines changed

rust/bazel/aspects.bzl

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
"""This file implements rust_proto_library aspect."""
99

10-
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
1110
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
1211
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
1312
load("@rules_rust//:version.bzl", RUST_VERSION = "VERSION")
@@ -17,9 +16,6 @@ load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVari
1716

1817
# buildifier: disable=bzl-visibility
1918
load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action")
20-
load("//bazel/common:proto_common.bzl", "proto_common")
21-
load("//bazel/common:proto_info.bzl", "ProtoInfo")
22-
load("//bazel/private:cc_proto_aspect.bzl", "cc_proto_aspect")
2319

2420
visibility(["//rust/...", "//third_party/crubit/rs_bindings_from_cc/..."])
2521

@@ -34,8 +30,13 @@ CrateMappingInfo = provider(
3430
RustProtoInfo = provider(
3531
doc = "Rust protobuf provider info",
3632
fields = {
37-
"dep_variant_infos": "List of DepVariantInfo for the compiled Rust " +
38-
"gencode (also covers its transitive dependencies)",
33+
"dep_variant_infos": "List(DepVariantInfo): infos for the compiled Rust gencode. \
34+
When the target is a regular proto library, this contains a single element -- the info for the top-level generated code. \
35+
When the target is a srcsless proto library, this contains the infos of its dependencies.",
36+
"exports_dep_variant_infos": "List(DepVariantInfo): Transitive infos from targets from the proto_library.exports attribute. \
37+
When the target is a srcsless proto library, this contains the exports infos from all of its dependencies. \
38+
This is a list instead of a depset, since we pass them as direct dependencies when compiling rust code. \
39+
We assume the proto exports feature is not widely used in a way where this will lead to unacceptable analysis-time overhead.",
3940
"crate_mapping": "depset(CrateMappingInfo) containing mappings of all transitive " +
4041
"dependencies of the current proto_library.",
4142
},
@@ -50,6 +51,9 @@ def _rust_version_ge(version):
5051
return _version_parts(RUST_VERSION) >= _version_parts(version)
5152

5253
def label_to_crate_name(ctx, label, toolchain):
54+
return compute_crate_name(ctx.workspace_name, label, toolchain)
55+
56+
def encode_label_as_crate_name(label):
5357
return label.name.replace("-", "_")
5458

5559
def proto_rust_toolchain_label(is_upb):
@@ -226,7 +230,7 @@ def _compile_cc(
226230
linking_context = linking_context,
227231
)
228232

229-
def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime):
233+
def _compile_rust(ctx, attr, src, extra_srcs, deps, aliases, runtime):
230234
"""Compiles a Rust source file.
231235
232236
Eventually this function could be upstreamed into rules_rust and be made present in rust_common.
@@ -237,6 +241,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime):
237241
src (File): The crate root source file to be compiled.
238242
extra_srcs ([File]): Additional source files to include in the crate.
239243
deps (List[DepVariantInfo]): A list of dependencies needed.
244+
aliases (dict[Target, str]): A mapping from dependency target to its crate name.
240245
runtime: The protobuf runtime target.
241246
242247
Returns:
@@ -245,7 +250,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime):
245250
toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
246251
output_hash = repr(hash(src.path))
247252

248-
crate_name = label_to_crate_name(ctx, ctx.label, toolchain)
253+
crate_name = compute_crate_name(ctx, ctx.label, toolchain)
249254

250255
lib_name = "{prefix}{name}-{lib_hash}{extension}".format(
251256
prefix = "lib",
@@ -292,7 +297,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime):
292297
# generated code to use a consistent name, even though the actual
293298
# name of the runtime crate varies depending on the protobuf kernel
294299
# and build system.
295-
aliases = {runtime: "protobuf"},
300+
aliases = {runtime: "protobuf"} | aliases,
296301
output = lib,
297302
metadata = rmeta,
298303
edition = "2024",
@@ -302,8 +307,6 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps, runtime):
302307
compile_data_targets = depset([]),
303308
owner = ctx.label,
304309
),
305-
# Needed to make transitive public imports not violate layering.
306-
force_all_deps_direct = True,
307310
output_hash = output_hash,
308311
)
309312

@@ -343,18 +346,36 @@ def _rust_proto_aspect_common(target, ctx, is_upb):
343346
transitive_crate_mappings.append(rust_proto_info.crate_mapping)
344347

345348
dep_variant_infos = []
346-
for info in [d[RustProtoInfo].dep_variant_infos for d in proto_deps]:
347-
dep_variant_infos += info
349+
350+
# Infos of exports of dependencies.
351+
dep_exports_dep_variant_infos = []
352+
353+
for dep in proto_deps:
354+
dep_variant_infos += dep[RustProtoInfo].dep_variant_infos
355+
dep_exports_dep_variant_infos += dep[RustProtoInfo].exports_dep_variant_infos
348356

349357
# If there are no srcs, then this is an alias library (which in Rust acts as a middle
350358
# library in a dependency chain). Don't generate any Rust code for it, but do propagate the
351359
# crate mappings.
352360
if not proto_srcs:
353361
return [RustProtoInfo(
354362
dep_variant_infos = dep_variant_infos,
363+
exports_dep_variant_infos = dep_exports_dep_variant_infos,
355364
crate_mapping = depset(transitive = transitive_crate_mappings),
356365
)]
357366

367+
# Add the infos from dependencies' exports, as they are needed to compile the
368+
# generated code of this target.
369+
dep_variant_infos += dep_exports_dep_variant_infos
370+
371+
# Exports of this target are the directly and transitively exported
372+
# dependencies.
373+
exported_proto_deps = getattr(ctx.rule.attr, "exports", [])
374+
exports_dep_variant_infos = []
375+
for d in exported_proto_deps:
376+
exports_dep_variant_infos.extend(d[RustProtoInfo].dep_variant_infos)
377+
exports_dep_variant_infos.extend(d[RustProtoInfo].exports_dep_variant_infos)
378+
358379
proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo]
359380
cc_toolchain = find_cpp_toolchain(ctx)
360381
toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
@@ -422,19 +443,35 @@ def _rust_proto_aspect_common(target, ctx, is_upb):
422443
for dep in ctx.attr._extra_deps
423444
]
424445

446+
aliases = {}
447+
448+
for d in dep_variant_infos:
449+
label = Label(d.crate_info.owner)
450+
target = struct(label = label)
451+
qualified_name = encode_label_as_crate_name(label)
452+
aliases[target] = qualified_name
453+
454+
deps = ([dep_variant_info_for_runtime] +
455+
dep_variant_info_for_native_gencode +
456+
dep_variant_infos +
457+
extra_dep_variant_infos +
458+
exports_dep_variant_infos)
459+
425460
dep_variant_info = _compile_rust(
426461
ctx = ctx,
427462
attr = ctx.rule.attr,
428463
src = entry_point_rs_output,
429464
extra_srcs = rs_gencode,
430-
deps = [dep_variant_info_for_runtime] + dep_variant_info_for_native_gencode + dep_variant_infos + extra_dep_variant_infos,
465+
deps = deps,
466+
aliases = aliases,
431467
runtime = runtime,
432468
)
433469
return [RustProtoInfo(
434470
dep_variant_infos = [dep_variant_info],
471+
exports_dep_variant_infos = exports_dep_variant_infos,
435472
crate_mapping = depset(
436473
direct = [CrateMappingInfo(
437-
crate_name = label_to_crate_name(ctx, target.label, toolchain),
474+
crate_name = encode_label_as_crate_name(ctx.label),
438475
import_paths = tuple([get_import_path(f) for f in proto_srcs]),
439476
)],
440477
transitive = transitive_crate_mappings,
@@ -444,7 +481,7 @@ def _rust_proto_aspect_common(target, ctx, is_upb):
444481
def _make_proto_library_aspect(is_upb):
445482
return aspect(
446483
implementation = (_rust_upb_proto_aspect_impl if is_upb else _rust_cc_proto_aspect_impl),
447-
attr_aspects = ["deps"],
484+
attr_aspects = ["deps", "exports"],
448485
requires = ([] if is_upb else [cc_proto_aspect]),
449486
attrs = {
450487
"_collect_cc_coverage": attr.label(
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Protocol Buffers - Google's data interchange format
2+
# Copyright 2023 Google LLC. All rights reserved.
3+
#
4+
# Use of this source code is governed by a BSD-style
5+
# license that can be found in the LICENSE file or at
6+
# https://developers.google.com/open-source/licenses/bsd
7+
8+
"""Implements encode_raw_string_as_crate_name.
9+
10+
Based on an implementation in rules_rust:
11+
* https://github.com/bazelbuild/rules_rust/blob/cdaf15f5796e3e934b074526272823284bbaed01/rust/private/utils.bzl#L643
12+
"""
13+
14+
# This is a list of pairs, where the first element of the pair is a character
15+
# that is allowed in Bazel package or target names but not in crate names; and
16+
# the second element is an encoding of that char suitable for use in a crate
17+
# name.
18+
_encodings = (
19+
(":", "x"),
20+
("!", "excl"),
21+
("%", "prc"),
22+
("@", "ao"),
23+
("^", "caret"),
24+
("`", "bt"),
25+
(" ", "sp"),
26+
("\"", "dq"),
27+
("#", "octo"),
28+
("$", "dllr"),
29+
("&", "amp"),
30+
("'", "sq"),
31+
("(", "lp"),
32+
(")", "rp"),
33+
("*", "astr"),
34+
("-", "d"),
35+
("+", "pl"),
36+
(",", "cm"),
37+
(";", "sm"),
38+
("<", "la"),
39+
("=", "eq"),
40+
(">", "ra"),
41+
("?", "qm"),
42+
("[", "lbk"),
43+
("]", "rbk"),
44+
("{", "lbe"),
45+
("|", "pp"),
46+
("}", "rbe"),
47+
("~", "td"),
48+
("/", "y"),
49+
(".", "pd"),
50+
)
51+
52+
# For each of the above encodings, we generate two substitution rules: one that
53+
# ensures any occurrences of the encodings themselves in the package/target
54+
# aren't clobbered by this translation, and one that does the encoding itself.
55+
# We also include a rule that protects the clobbering-protection rules from
56+
# getting clobbered.
57+
_substitutions = [("_z", "_zz_")] + [
58+
subst
59+
for (pattern, replacement) in _encodings
60+
for subst in (
61+
("_{}_".format(replacement), "_z{}_".format(replacement)),
62+
(pattern, "_{}_".format(replacement)),
63+
)
64+
]
65+
66+
# Expose the substitutions for testing only.
67+
substitutions_for_testing = _substitutions
68+
69+
def _replace_all(string, substitutions):
70+
"""Replaces occurrences of the given patterns in `string`.
71+
72+
There are a few reasons this looks complicated:
73+
* The substitutions are performed with some priority, i.e. patterns that are
74+
listed first in `substitutions` are higher priority than patterns that are
75+
listed later.
76+
* We also take pains to avoid doing replacements that overlap with each
77+
other, since overlaps invalidate pattern matches.
78+
* To avoid hairy offset invalidation, we apply the substitutions
79+
right-to-left.
80+
* To avoid the "_quote" -> "_quotequote_" rule introducing new pattern
81+
matches later in the string during decoding, we take the leftmost
82+
replacement, in cases of overlap. (Note that no rule can induce new
83+
pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to
84+
"_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in
85+
this string, and overlap.).
86+
87+
Args:
88+
string (string): the string in which the replacements should be performed.
89+
substitutions: the list of patterns and replacements to apply.
90+
91+
Returns:
92+
A string with the appropriate substitutions performed.
93+
"""
94+
95+
# Find the highest-priority pattern matches for each string index, going
96+
# left-to-right and skipping indices that are already involved in a
97+
# pattern match.
98+
plan = {}
99+
matched_indices_set = {}
100+
for pattern_start in range(len(string)):
101+
if pattern_start in matched_indices_set:
102+
continue
103+
for (pattern, replacement) in substitutions:
104+
if not string.startswith(pattern, pattern_start):
105+
continue
106+
length = len(pattern)
107+
plan[pattern_start] = (length, replacement)
108+
matched_indices_set.update([(pattern_start + i, True) for i in range(length)])
109+
break
110+
111+
# Execute the replacement plan, working from right to left.
112+
for pattern_start in sorted(plan.keys(), reverse = True):
113+
length, replacement = plan[pattern_start]
114+
after_pattern = pattern_start + length
115+
string = string[:pattern_start] + replacement + string[after_pattern:]
116+
117+
return string
118+
119+
def encode_raw_string_as_crate_name(str):
120+
"""Encodes a string using the above encoding format.
121+
122+
Args:
123+
str (string): The string to be encoded.
124+
125+
Returns:
126+
An encoded version of the input string.
127+
"""
128+
return _replace_all(str, _substitutions)
129+
130+
def decode_crate_name_as_raw_string_for_testing(crate_name):
131+
"""Decodes a crate_name that was encoded by encode_raw_string_as_crate_name.
132+
133+
This is used to check that the encoding is bijective; it is expected to only
134+
be used in tests.
135+
136+
Args:
137+
crate_name (string): The name of the crate.
138+
139+
Returns:
140+
A string representing the Bazel label (package and target).
141+
"""
142+
return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions])

0 commit comments

Comments
 (0)