Skip to content

Commit 443b089

Browse files
authored
rust_analyzer: don't build a tree of RustAnalyzerInfos (#3028)
This patch adds an `id` to RustAnalyzerInfo, and replace all the recursive RustAnalyzerInfos with the respective crate `id`s. This fixes RustAnalyzerInfos becoming exponentially expensive to build in the presence of aliases. --- Before this patch, `RustAnalyzerInfo.deps` contains the RustAnalyzerInfo of the target crate's deps, and so on recursively. This forms a graph of Infos where there is one Info per target, but multiple paths from one target to another. e.g. these could all point to `bar`: `foo.deps[0] == foo.deps[1].deps[0] == foo.aliases[0]`. If we walk `foo` as a tree, we will see `bar` multiple times. Two operations that do this are `print(info)` and `{info: 42}` which hashes the object. As the graph grows, the number of paths grows combinatorically. `RustAnalyzerInfo.aliases` is defined as a dict from `RustAnalyzerInfo => string`, so building this triggers the slowdown. It would be possible to fix this by e.g. replacing this dict with a list of pairs. However the work `rust_analyzer_aspect` does is fundamentally local and does not need a recursive data structure. Eliminating it means we can freely use these values as keys, print them, etc. --- Timings for `ra_ap_rust-analyzer` (which heavily aliases its deps): ``` blaze build //third_party/bazel_rules/rules_rust/tools/rust_analyzer:gen_rust_project; \ time blaze run //third_party/bazel_rules/rules_rust/tools/rust_analyzer:gen_rust_project -- //third_party/rust/ra_ap_rust_analyzer/... ===Before=== Executed in 211.06 secs fish external usr time 0.24 secs 0.00 micros 0.24 secs sys time 1.24 secs 604.00 micros 1.24 sec ===After=== Executed in 3.24 secs fish external usr time 0.15 secs 389.00 micros 0.15 secs sys time 1.18 secs 125.00 micros 1.18 secs ```
1 parent 6cfd508 commit 443b089

File tree

3 files changed

+45
-46
lines changed

3 files changed

+45
-46
lines changed

extensions/prost/private/prost.bzl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,10 @@ def _rust_prost_aspect_impl(target, ctx):
261261
# https://github.com/rust-analyzer/rust-analyzer/blob/2021-11-15/crates/project_model/src/workspace.rs#L529-L531
262262
cfgs = ["test", "debug_assertions"]
263263

264+
crate_id = "prost-" + dep_variant_info.crate_info.root.path
265+
264266
rust_analyzer_info = write_rust_analyzer_spec_file(ctx, ctx.rule.attr, ctx.label, RustAnalyzerInfo(
267+
id = crate_id,
265268
aliases = {},
266269
crate = dep_variant_info.crate_info,
267270
cfgs = cfgs,
@@ -332,7 +335,10 @@ def _rust_prost_library_impl(ctx):
332335
transitive = transitive,
333336
),
334337
),
335-
RustAnalyzerGroupInfo(deps = [proto_dep[RustAnalyzerInfo]]),
338+
RustAnalyzerGroupInfo(
339+
crate_specs = proto_dep[RustAnalyzerInfo].crate_specs,
340+
deps = proto_dep[RustAnalyzerInfo].deps,
341+
),
336342
]
337343

338344
rust_prost_library = rule(

rust/private/providers.bzl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,22 @@ TestCrateInfo = provider(
157157
RustAnalyzerInfo = provider(
158158
doc = "RustAnalyzerInfo holds rust crate metadata for targets",
159159
fields = {
160-
"aliases": "Dict[RustAnalyzerInfo, String]: Replacement names these targets should be known as in Rust code",
160+
"aliases": "Dict[String, String]: Maps crate IDs to Replacement names these targets should be known as in Rust code",
161161
"build_info": "BuildInfo: build info for this crate if present",
162162
"cfgs": "List[String]: features or other compilation `--cfg` settings",
163163
"crate": "CrateInfo: Crate information.",
164164
"crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
165-
"deps": "List[RustAnalyzerInfo]: direct dependencies",
165+
"deps": "List[String]: IDs of direct dependency crates",
166166
"env": "Dict[String: String]: Environment variables, used for the `env!` macro",
167+
"id": "String: Arbitrary unique ID for this crate",
167168
"proc_macro_dylib_path": "File: compiled shared library output of proc-macro rule",
168169
},
169170
)
170171

171172
RustAnalyzerGroupInfo = provider(
172173
doc = "RustAnalyzerGroupInfo holds multiple RustAnalyzerInfos",
173174
fields = {
174-
"deps": "List[RustAnalyzerInfo]: direct dependencies",
175+
"crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
176+
"deps": "List[String]: crate IDs of direct dependencies",
175177
},
176178
)

rust/private/rust_analyzer.bzl

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def write_rust_analyzer_spec_file(ctx, attrs, owner, base_info):
5353
cfgs = base_info.cfgs,
5454
env = base_info.env,
5555
deps = base_info.deps,
56+
id = base_info.id,
5657
crate_specs = depset(direct = [crate_spec], transitive = [base_info.crate_specs]),
5758
proc_macro_dylib_path = base_info.proc_macro_dylib_path,
5859
build_info = base_info.build_info,
@@ -72,21 +73,6 @@ def write_rust_analyzer_spec_file(ctx, attrs, owner, base_info):
7273

7374
return rust_analyzer_info
7475

75-
def _accumulate_rust_analyzer_info(dep_infos_to_accumulate, label_index_to_accumulate, dep):
76-
if dep == None:
77-
return
78-
if RustAnalyzerInfo in dep:
79-
label_index_to_accumulate[dep.label] = dep[RustAnalyzerInfo]
80-
dep_infos_to_accumulate.append(dep[RustAnalyzerInfo])
81-
if RustAnalyzerGroupInfo in dep:
82-
for expanded_dep in dep[RustAnalyzerGroupInfo].deps:
83-
label_index_to_accumulate[expanded_dep.crate.owner] = expanded_dep
84-
dep_infos_to_accumulate.append(expanded_dep)
85-
86-
def _accumulate_rust_analyzer_infos(dep_infos_to_accumulate, label_index_to_accumulate, deps_attr):
87-
for dep in deps_attr:
88-
_accumulate_rust_analyzer_info(dep_infos_to_accumulate, label_index_to_accumulate, dep)
89-
9076
def _rust_analyzer_aspect_impl(target, ctx):
9177
if (rust_common.crate_info not in target and
9278
rust_common.test_crate_info not in target and
@@ -107,41 +93,55 @@ def _rust_analyzer_aspect_impl(target, ctx):
10793
cfgs += [f[6:] for f in ctx.rule.attr.rustc_flags if f.startswith("--cfg ") or f.startswith("--cfg=")]
10894

10995
build_info = None
110-
dep_infos = []
111-
labels_to_rais = {}
112-
11396
for dep in getattr(ctx.rule.attr, "deps", []):
11497
# Save BuildInfo if we find any (for build script output)
11598
if BuildInfo in dep:
11699
build_info = dep[BuildInfo]
117100

118-
_accumulate_rust_analyzer_infos(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "deps", []))
119-
_accumulate_rust_analyzer_infos(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "proc_macro_deps", []))
120-
121-
_accumulate_rust_analyzer_info(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "crate", None))
122-
_accumulate_rust_analyzer_info(dep_infos, labels_to_rais, getattr(ctx.rule.attr, "actual", None))
101+
# Gather required info from dependencies.
102+
label_to_id = {} # {Label of dependency => crate_id}
103+
crate_specs = [] # [depset of File - transitive crate_spec.json files]
104+
attrs = ctx.rule.attr
105+
all_deps = getattr(attrs, "deps", []) + getattr(attrs, "proc_macro_deps", []) + \
106+
[dep for dep in [getattr(attrs, "crate", None), getattr(attrs, "actual", None)] if dep != None]
107+
for dep in all_deps:
108+
if RustAnalyzerInfo in dep:
109+
label_to_id[dep.label] = dep[RustAnalyzerInfo].id
110+
crate_specs.append(dep[RustAnalyzerInfo].crate_specs)
111+
if RustAnalyzerGroupInfo in dep:
112+
for expanded_dep in dep[RustAnalyzerGroupInfo].deps:
113+
label_to_id[expanded_dep] = expanded_dep
114+
crate_specs.append(dep[RustAnalyzerGroupInfo].crate_specs)
115+
116+
deps = label_to_id.values()
117+
crate_specs = depset(transitive = crate_specs)
123118

124119
if rust_common.crate_group_info in target:
125-
return [RustAnalyzerGroupInfo(deps = dep_infos)]
120+
return [RustAnalyzerGroupInfo(deps = deps, crate_specs = crate_specs)]
126121
elif rust_common.crate_info in target:
127122
crate_info = target[rust_common.crate_info]
128123
elif rust_common.test_crate_info in target:
129124
crate_info = target[rust_common.test_crate_info].crate
130125
else:
131126
fail("Unexpected target type: {}".format(target))
132127

133-
aliases = {}
134-
for aliased_target, aliased_name in getattr(ctx.rule.attr, "aliases", {}).items():
135-
if aliased_target.label in labels_to_rais:
136-
aliases[labels_to_rais[aliased_target.label]] = aliased_name
128+
aliases = {
129+
label_to_id[target.label]: name
130+
for (target, name) in getattr(attrs, "aliases", {}).items()
131+
if target.label in label_to_id
132+
}
133+
134+
# An arbitrary unique and stable identifier.
135+
crate_id = "ID-" + crate_info.root.path
137136

138137
rust_analyzer_info = write_rust_analyzer_spec_file(ctx, ctx.rule.attr, ctx.label, RustAnalyzerInfo(
138+
id = crate_id,
139139
aliases = aliases,
140140
crate = crate_info,
141141
cfgs = cfgs,
142142
env = crate_info.rustc_env,
143-
deps = dep_infos,
144-
crate_specs = depset(transitive = [dep.crate_specs for dep in dep_infos]),
143+
deps = deps,
144+
crate_specs = crate_specs,
145145
proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target),
146146
build_info = build_info,
147147
))
@@ -193,14 +193,6 @@ _WORKSPACE_TEMPLATE = "__WORKSPACE__/"
193193
_EXEC_ROOT_TEMPLATE = "__EXEC_ROOT__/"
194194
_OUTPUT_BASE_TEMPLATE = "__OUTPUT_BASE__/"
195195

196-
def _crate_id(crate_info):
197-
"""Returns a unique stable identifier for a crate
198-
199-
Returns:
200-
(string): This crate's unique stable id.
201-
"""
202-
return "ID-" + crate_info.root.path
203-
204196
def _create_single_crate(ctx, attrs, info):
205197
"""Creates a crate in the rust-project.json format.
206198
@@ -214,8 +206,7 @@ def _create_single_crate(ctx, attrs, info):
214206
"""
215207
crate_name = info.crate.name
216208
crate = dict()
217-
crate_id = _crate_id(info.crate)
218-
crate["crate_id"] = crate_id
209+
crate["crate_id"] = info.id
219210
crate["display_name"] = crate_name
220211
crate["edition"] = info.crate.edition
221212
crate["env"] = {}
@@ -263,8 +254,8 @@ def _create_single_crate(ctx, attrs, info):
263254
# There's one exception - if the dependency is the same crate name as the
264255
# the crate being processed, we don't add it as a dependency to itself. This is
265256
# common and expected - `rust_test.crate` pointing to the `rust_library`.
266-
crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
267-
crate["aliases"] = {_crate_id(alias_target.crate): alias_name for alias_target, alias_name in info.aliases.items()}
257+
crate["deps"] = [id for id in info.deps if id != info.id]
258+
crate["aliases"] = info.aliases
268259
crate["cfg"] = info.cfgs
269260
toolchain = find_toolchain(ctx)
270261
crate["target"] = (_EXEC_ROOT_TEMPLATE + toolchain.target_json.path) if toolchain.target_json else toolchain.target_flag_value

0 commit comments

Comments
 (0)