Skip to content

Commit caf4106

Browse files
committed
Support bundling all transitive crates in one directory
Transitive crate dependencies are passed to rustc by specifying the folder they live in as a library search path (an `-Ldependency=path` flag). Given that bazel builds all libraries into their own folders, this means passing potentially hundreds of paths to rustc. For a number of transitive dependencies greater than 400 we've observed that rustc runs into some odd `LoadLibraryExW` error and fails to find some transitive crates and thus fails to build. For this reason, this patch can optionally createc symlinks to all the transitive crates into a single directory that it then sets as the `-Ldependency=` flag to rustc. We only do this when building on Windows and when the number of transitive crates is greater than 400, which happens for a handful of top-level crates in the dropbox_core project.
1 parent de726a1 commit caf4106

File tree

3 files changed

+81
-13
lines changed

3 files changed

+81
-13
lines changed

rust/private/clippy.bzl

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _clippy_aspect_impl(target, ctx):
8383
build_info,
8484
)
8585

86-
args, env = construct_arguments(
86+
args, env, extra_link_inputs = construct_arguments(
8787
ctx = ctx,
8888
attr = ctx.rule.attr,
8989
file = ctx.file,
@@ -102,6 +102,7 @@ def _clippy_aspect_impl(target, ctx):
102102
build_flags_files = build_flags_files,
103103
emit = ["dep-info", "metadata"],
104104
)
105+
compile_inputs = depset(extra_link_inputs, transitive = [compile_inputs])
105106

106107
if crate_info.is_test:
107108
args.rustc_flags.add("--test")

rust/private/rustc.bzl

+77-11
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ def construct_arguments(
631631
- all (list): A list of all `Args` objects in the order listed above.
632632
This is to be passed to the `arguments` parameter of actions
633633
- (dict): Common rustc environment variables
634+
- (list): Extra input files for the compile action
634635
"""
635636
output_dir = getattr(crate_info.output, "dirname", None)
636637
linker_script = getattr(file, "linker_script", None)
@@ -764,7 +765,7 @@ def construct_arguments(
764765
_add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration)
765766

766767
# These always need to be added, even if not linking this crate.
767-
add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct)
768+
extra_link_inputs = add_crate_link_flags(ctx, toolchain, rustc_flags, crate_info, dep_info, force_all_deps_direct)
768769

769770
needs_extern_proc_macro_flag = "proc-macro" in [crate_info.type, crate_info.wrapped_crate_type] and \
770771
crate_info.edition != "2015"
@@ -810,7 +811,7 @@ def construct_arguments(
810811
all = [process_wrapper_flags, rustc_path, rustc_flags],
811812
)
812813

813-
return args, env
814+
return args, env, extra_link_inputs
814815

815816
def rustc_compile_action(
816817
ctx,
@@ -867,7 +868,7 @@ def rustc_compile_action(
867868
stamp = stamp,
868869
)
869870

870-
args, env_from_args = construct_arguments(
871+
args, env_from_args, extra_link_inputs = construct_arguments(
871872
ctx = ctx,
872873
attr = attr,
873874
file = ctx.file,
@@ -887,6 +888,7 @@ def rustc_compile_action(
887888
force_all_deps_direct = force_all_deps_direct,
888889
stamp = stamp,
889890
)
891+
compile_inputs = depset(extra_link_inputs, transitive = [compile_inputs])
890892

891893
env = dict(ctx.configuration.default_shell_env)
892894
env.update(env_from_args)
@@ -1179,16 +1181,71 @@ def _get_dir_names(files):
11791181
dirs[f.dirname] = None
11801182
return dirs.keys()
11811183

1182-
def add_crate_link_flags(args, dep_info, force_all_deps_direct = False):
1184+
def _symlink_transitive_crates_if_needed(ctx, toolchain, crate_info, dep_info):
1185+
"""Collect and symlink the transitive crates into a single directory.
1186+
1187+
The reason for this is that when passing a large (350+) number of `-Ldependency=` arguments to rustc on
1188+
Windows, we seem to be getting some obscure errors such as "failure to call `LoadLibraryExW` that results
1189+
on some transitive dependencies not being picked up and the build to fail.
1190+
1191+
If we detect a large number of transitive dependencies we symlink them all into a single directory that
1192+
we can pass to rustc in a single `-Ldependency=` argument.
1193+
1194+
Args:
1195+
ctx (ctx): The rule's context object
1196+
toolchain (rust_toolchain): The current `rust_toolchain`
1197+
crate_info (CrateInfo): The CrateInfo provider of the target crate
1198+
dep_info (DepInfo): The current target's dependency info
1199+
1200+
Returns:
1201+
tuple: A tuple of the following items
1202+
- (File): Optional - the output directory containing all transitive crates that this crate depends on.
1203+
- (list): The list of transitive crates files that should be used as input to the build action.
1204+
"""
1205+
if toolchain.os != "windows":
1206+
return None, []
1207+
1208+
deps = dep_info.transitive_crates.to_list()
1209+
1210+
# Only symlink if we are about to pass a large (350+) number of transitive dependencies to rustc.
1211+
if not deps or len(deps) < 350:
1212+
return None, []
1213+
1214+
output_dirname = crate_info.output.basename + ".transitive_crates"
1215+
links = []
1216+
1217+
# Keep a list of the crates that were currently added so that we can uniquify them.
1218+
names = {}
1219+
1220+
for dep in deps:
1221+
name = dep.output.basename
1222+
if name in names:
1223+
continue
1224+
1225+
link = ctx.actions.declare_file(output_dirname + "/" + name)
1226+
ctx.actions.symlink(output = link, target_file = dep.output, is_executable = True)
1227+
1228+
names[name] = True
1229+
links.append(link)
1230+
1231+
return links[0].dirname, links
1232+
1233+
def add_crate_link_flags(ctx, toolchain, args, crate_info, dep_info, force_all_deps_direct = False):
11831234
"""Adds link flags to an Args object reference
11841235
11851236
Args:
1237+
ctx (ctx): The rule's context object
1238+
toolchain (rust_toolchain): The current `rust_toolchain`
11861239
args (Args): An arguments object reference
1240+
crate_info (CrateInfo): The CrateInfo provider for the current target.
11871241
dep_info (DepInfo): The current target's dependency info
1242+
If this argument is set, only it will be added as a `-Ldependency=` flag, otherwise all rlibs will be set individually.
11881243
force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
11891244
to the commandline as opposed to -L.
1190-
"""
11911245
1246+
Returns:
1247+
- (list): A list of extra inputs that should be added to the rustc compile action.
1248+
"""
11921249
if force_all_deps_direct:
11931250
args.add_all(
11941251
depset(
@@ -1203,12 +1260,21 @@ def add_crate_link_flags(args, dep_info, force_all_deps_direct = False):
12031260
else:
12041261
# nb. Direct crates are linked via --extern regardless of their crate_type
12051262
args.add_all(dep_info.direct_crates, map_each = _crate_to_link_flag)
1206-
args.add_all(
1207-
dep_info.transitive_crates,
1208-
map_each = _get_crate_dirname,
1209-
uniquify = True,
1210-
format_each = "-Ldependency=%s",
1211-
)
1263+
1264+
transitive_crates_dir, transitive_crates_links = _symlink_transitive_crates_if_needed(ctx, toolchain, crate_info, dep_info)
1265+
1266+
# If transitive rlibs have been collected into this single directory, only set this directory
1267+
if transitive_crates_dir:
1268+
args.add("-Ldependency={}".format(transitive_crates_dir))
1269+
else:
1270+
args.add_all(
1271+
dep_info.transitive_crates,
1272+
map_each = _get_crate_dirname,
1273+
uniquify = True,
1274+
format_each = "-Ldependency=%s",
1275+
)
1276+
1277+
return transitive_crates_links
12121278

12131279
def _crate_to_link_flag(crate):
12141280
"""A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object

rust/private/rustdoc.bzl

+2-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def rustdoc_compile_action(
9898
# arguments expecting to do so.
9999
rustdoc_crate_info = _strip_crate_info_output(crate_info)
100100

101-
args, env = construct_arguments(
101+
args, env, extra_link_inputs = construct_arguments(
102102
ctx = ctx,
103103
attr = ctx.attr,
104104
file = ctx.file,
@@ -119,6 +119,7 @@ def rustdoc_compile_action(
119119
remap_path_prefix = None,
120120
force_link = True,
121121
)
122+
compile_inputs = depset(extra_link_inputs, transitive = [compile_inputs])
122123

123124
# Because rustdoc tests compile tests outside of the sandbox, the sysroot
124125
# must be updated to the `short_path` equivilant as it will now be

0 commit comments

Comments
 (0)