Skip to content

Commit 2c88055

Browse files
authored
Make sure each linkstamp is compiled just once. (#968)
1 parent 4efefd6 commit 2c88055

File tree

3 files changed

+76
-36
lines changed

3 files changed

+76
-36
lines changed

rust/private/clippy.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,21 @@ def _clippy_aspect_impl(target, ctx):
6363
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
6464
crate_type = crate_info.type
6565

66-
dep_info, build_info = collect_deps(
66+
dep_info, build_info, linkstamps = collect_deps(
6767
label = ctx.label,
6868
deps = crate_info.deps,
6969
proc_macro_deps = crate_info.proc_macro_deps,
7070
aliases = crate_info.aliases,
71+
# Clippy doesn't need to invoke transitive linking, therefore doesn't need linkstamps.
72+
are_linkstamps_supported = False,
7173
make_rust_providers_target_independent = toolchain._incompatible_make_rust_providers_target_independent,
7274
)
7375

7476
compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs = collect_inputs(
7577
ctx,
7678
ctx.rule.file,
7779
ctx.rule.files,
80+
linkstamps,
7881
toolchain,
7982
cc_toolchain,
8083
feature_configuration,

rust/private/rustc.bzl

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -102,26 +102,41 @@ def get_compilation_mode_opts(ctx, toolchain):
102102

103103
return toolchain.compilation_mode_opts[comp_mode]
104104

105-
def collect_deps(label, deps, proc_macro_deps, aliases, make_rust_providers_target_independent = False):
105+
def _are_linkstamps_supported(feature_configuration, has_grep_includes):
106+
# Are linkstamps supported by the C++ toolchain?
107+
return (cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "linkstamps") and
108+
# Is Bazel recent enough to support Starlark linkstamps?
109+
hasattr(cc_common, "register_linkstamp_compile_action") and
110+
# The current rule doesn't define _grep_includes attribute; this
111+
# attribute is required for compiling linkstamps.
112+
has_grep_includes)
113+
114+
def collect_deps(label, deps, proc_macro_deps, aliases, are_linkstamps_supported = False, make_rust_providers_target_independent = False):
106115
"""Walks through dependencies and collects the transitive dependencies.
107116
108117
Args:
109118
label (str): Label of the current target.
110119
deps (list): The deps from ctx.attr.deps.
111120
proc_macro_deps (list): The proc_macro deps from ctx.attr.proc_macro_deps.
112121
aliases (dict): A dict mapping aliased targets to their actual Crate information.
122+
are_linkstamps_supported (bool): Whether the current rule and the toolchain support building linkstamps.
113123
make_rust_providers_target_independent (bool): Whether
114124
--incompatible_make_rust_providers_target_independent has been flipped.
115125
116126
Returns:
117-
tuple: Returns a tuple (DepInfo, BuildInfo) of providers.
127+
tuple: Returns a tuple of:
128+
DepInfo,
129+
BuildInfo,
130+
linkstamps (depset[CcLinkstamp]): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
131+
118132
"""
119133
direct_crates = []
120134
transitive_crates = []
121135
transitive_noncrates = []
122136
transitive_noncrate_libs = []
123137
transitive_build_infos = []
124138
build_info = None
139+
linkstamps = []
125140

126141
aliases = {k.label: v for k, v in aliases.items()}
127142
for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
@@ -159,6 +174,8 @@ def collect_deps(label, deps, proc_macro_deps, aliases, make_rust_providers_targ
159174
libs = [get_preferred_artifact(lib) for li in linker_inputs for lib in li.libraries]
160175
transitive_noncrate_libs.append(depset(libs))
161176
transitive_noncrates.append(cc_info.linking_context.linker_inputs)
177+
if are_linkstamps_supported:
178+
linkstamps.append(cc_info.linking_context.linkstamps())
162179
elif dep_build_info:
163180
if build_info:
164181
fail("Several deps are providing build information, " +
@@ -188,6 +205,7 @@ def collect_deps(label, deps, proc_macro_deps, aliases, make_rust_providers_targ
188205
dep_env = build_info.dep_env if build_info else None,
189206
),
190207
build_info,
208+
depset(transitive = linkstamps),
191209
)
192210

193211
def _get_crate_and_dep_info(dep):
@@ -305,6 +323,7 @@ def collect_inputs(
305323
ctx,
306324
file,
307325
files,
326+
linkstamps,
308327
toolchain,
309328
cc_toolchain,
310329
feature_configuration,
@@ -317,6 +336,7 @@ def collect_inputs(
317336
ctx (ctx): The rule's context object.
318337
file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
319338
files (list): A list of all inputs (`ctx.files`).
339+
linkstamps (depset): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
320340
toolchain (rust_toolchain): The current `rust_toolchain`.
321341
cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
322342
feature_configuration (FeatureConfiguration): Feature configuration to be queried.
@@ -371,38 +391,33 @@ def collect_inputs(
371391
],
372392
)
373393

374-
if (crate_info.type in ("bin", "cdylib") and
375-
# Are linkstamps supported by the C++ toolchain?
376-
cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "linkstamps") and
377-
# Is Bazel recent enough to support Starlark linkstamps?
378-
hasattr(cc_common, "register_linkstamp_compile_action") and
379-
# The current rule doesn't define _grep_includes attribute; this
380-
# attribute is required for compiling linkstamps.
381-
hasattr(ctx.attr, "_grep_includes")):
382-
for dep in ctx.attr.deps:
383-
if CcInfo in dep and dep[CcInfo].linking_context:
384-
linking_context = dep[CcInfo].linking_context
385-
for linkstamp in linking_context.linkstamps().to_list():
386-
# The linkstamp output path is based on the binary crate
387-
# name and the input linkstamp path. This is to disambiguate
388-
# the linkstamp outputs produced by multiple binary crates
389-
# that depend on the same linkstamp. We use the same pattern
390-
# for the output name as the one used by native cc rules.
391-
out_name = "_objs/" + crate_info.output.basename + "/" + linkstamp.file().path[:-len(linkstamp.file().extension)] + "o"
392-
linkstamp_out = ctx.actions.declare_file(out_name)
393-
linkstamp_outs.append(linkstamp_out)
394-
cc_common.register_linkstamp_compile_action(
395-
actions = ctx.actions,
396-
cc_toolchain = cc_toolchain,
397-
feature_configuration = feature_configuration,
398-
grep_includes = ctx.file._grep_includes,
399-
source_file = linkstamp.file(),
400-
output_file = linkstamp_out,
401-
compilation_inputs = linkstamp.hdrs(),
402-
inputs_for_validation = nolinkstamp_compile_inputs,
403-
label_replacement = str(ctx.label),
404-
output_replacement = crate_info.output.path,
405-
)
394+
if crate_info.type in ("bin", "cdylib"):
395+
# There is no other way to register an action for each member of a depset than
396+
# flattening the depset as of 2021-10-12. Luckily, usually there is only one linkstamp
397+
# in a build, and we only flatten the list on binary targets that perform transitive linking,
398+
# so it's extremely unlikely that this call to `to_list()` will ever be a performance
399+
# problem.
400+
for linkstamp in linkstamps.to_list():
401+
# The linkstamp output path is based on the binary crate
402+
# name and the input linkstamp path. This is to disambiguate
403+
# the linkstamp outputs produced by multiple binary crates
404+
# that depend on the same linkstamp. We use the same pattern
405+
# for the output name as the one used by native cc rules.
406+
out_name = "_objs/" + crate_info.output.basename + "/" + linkstamp.file().path[:-len(linkstamp.file().extension)] + "o"
407+
linkstamp_out = ctx.actions.declare_file(out_name)
408+
linkstamp_outs.append(linkstamp_out)
409+
cc_common.register_linkstamp_compile_action(
410+
actions = ctx.actions,
411+
cc_toolchain = cc_toolchain,
412+
feature_configuration = feature_configuration,
413+
grep_includes = ctx.file._grep_includes,
414+
source_file = linkstamp.file(),
415+
output_file = linkstamp_out,
416+
compilation_inputs = linkstamp.hdrs(),
417+
inputs_for_validation = nolinkstamp_compile_inputs,
418+
label_replacement = str(ctx.label),
419+
output_replacement = crate_info.output.path,
420+
)
406421

407422
compile_inputs = depset(
408423
linkstamp_outs,
@@ -654,18 +669,23 @@ def rustc_compile_action(
654669

655670
make_rust_providers_target_independent = toolchain._incompatible_make_rust_providers_target_independent
656671

657-
dep_info, build_info = collect_deps(
672+
dep_info, build_info, linkstamps = collect_deps(
658673
label = ctx.label,
659674
deps = crate_info.deps,
660675
proc_macro_deps = crate_info.proc_macro_deps,
661676
aliases = crate_info.aliases,
677+
are_linkstamps_supported = _are_linkstamps_supported(
678+
feature_configuration = feature_configuration,
679+
has_grep_includes = hasattr(ctx.attr, "_grep_includes"),
680+
),
662681
make_rust_providers_target_independent = make_rust_providers_target_independent,
663682
)
664683

665684
compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs = collect_inputs(
666685
ctx = ctx,
667686
file = ctx.file,
668687
files = ctx.files,
688+
linkstamps = linkstamps,
669689
toolchain = toolchain,
670690
cc_toolchain = cc_toolchain,
671691
feature_configuration = feature_configuration,

test/unit/linkstamps/linkstamps_test.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,23 @@ def _linkstamps_test():
6464
}),
6565
)
6666

67+
cc_library(
68+
name = "cc_lib_with_linkstamp_transitively",
69+
deps = [":cc_lib_with_linkstamp"],
70+
)
71+
6772
rust_binary(
6873
name = "some_rust_binary",
6974
srcs = ["foo.rs"],
7075
deps = [":cc_lib_with_linkstamp"],
7176
)
7277

78+
rust_binary(
79+
name = "some_rust_binary_with_multiple_paths_to_a_linkstamp",
80+
srcs = ["foo.rs"],
81+
deps = [":cc_lib_with_linkstamp", ":cc_lib_with_linkstamp_transitively"],
82+
)
83+
7384
rust_test(
7485
name = "some_rust_test1",
7586
srcs = ["foo.rs"],
@@ -87,6 +98,11 @@ def _linkstamps_test():
8798
target_under_test = ":some_rust_binary",
8899
)
89100

101+
supports_linkstamps_test(
102+
name = "rust_binary_supports_duplicated_linkstamps",
103+
target_under_test = ":some_rust_binary_with_multiple_paths_to_a_linkstamp",
104+
)
105+
90106
supports_linkstamps_test(
91107
name = "rust_test_supports_linkstamps_test1",
92108
target_under_test = ":some_rust_test1",
@@ -116,6 +132,7 @@ def linkstamps_test_suite(name):
116132
name = name,
117133
tests = [
118134
":rust_binary_supports_linkstamps_test",
135+
":rust_binary_supports_duplicated_linkstamps",
119136
":rust_test_supports_linkstamps_test1",
120137
":rust_test_supports_linkstamps_test2",
121138
],

0 commit comments

Comments
 (0)