Skip to content

Commit a9565ff

Browse files
authored
Make test binary names predictable (#3899)
This PR makes `rust_test` binary names and output paths deterministic by removing the hash from the test compilation output directory. Instead of putting test outputs in a hashed subdirectory (e.g. `my/package/test-<hash>/`), the rules now use a predictable naming scheme (e.g. a `_test` suffix on the crate name) so test binaries and their outputs have stable, reproducible paths. closes #2827
1 parent 274309f commit a9565ff

13 files changed

Lines changed: 129 additions & 138 deletions

File tree

extensions/wasm_bindgen/private/wasm_bindgen_test.bzl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,7 @@ def _rust_wasm_bindgen_test_binary_impl(ctx):
7676

7777
output_hash = determine_output_hash(crate.root, ctx.label)
7878
output = ctx.actions.declare_file(
79-
"test-%s/%s%s" % (
80-
output_hash,
81-
ctx.label.name,
82-
toolchain.binary_ext,
83-
),
79+
ctx.label.name + toolchain.binary_ext,
8480
)
8581

8682
srcs = crate.srcs.to_list()
@@ -136,6 +132,7 @@ def _rust_wasm_bindgen_test_binary_impl(ctx):
136132
attr = ctx.attr,
137133
toolchain = toolchain,
138134
crate_info_dict = crate_info_dict,
135+
output_hash = output_hash,
139136
rust_flags = get_rust_test_flags(ctx.attr),
140137
skip_expanding_rustc_env = True,
141138
)

rust/private/rust.bzl

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -371,21 +371,11 @@ def _rust_test_impl(ctx):
371371
# Target is building the crate in `test` config
372372
crate = ctx.attr.crate[rust_common.crate_info] if rust_common.crate_info in ctx.attr.crate else ctx.attr.crate[rust_common.test_crate_info].crate
373373

374-
if toolchain._incompatible_change_rust_test_compilation_output_directory:
375-
crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
376-
output = ctx.actions.declare_file(
377-
ctx.label.name + toolchain.binary_ext,
378-
)
379-
else:
380-
crate_name = crate.name
381-
output_hash = determine_output_hash(crate.root, ctx.label)
382-
output = ctx.actions.declare_file(
383-
"test-%s/%s%s" % (
384-
output_hash,
385-
ctx.label.name,
386-
toolchain.binary_ext,
387-
),
388-
)
374+
crate_name = crate.name
375+
output_hash = determine_output_hash(crate.root, ctx.label)
376+
output = ctx.actions.declare_file(
377+
ctx.label.name + toolchain.binary_ext,
378+
)
389379

390380
rust_metadata = None
391381
rustc_rmeta_output = None
@@ -452,19 +442,10 @@ def _rust_test_impl(ctx):
452442
crate_root = crate_root_src(ctx.attr.name, ctx.attr.crate_name, ctx.files.srcs, crate_root_type)
453443
srcs, compile_data, crate_root = transform_sources(ctx, ctx.files.srcs, ctx.files.compile_data, crate_root)
454444

455-
if toolchain._incompatible_change_rust_test_compilation_output_directory:
456-
output = ctx.actions.declare_file(
457-
ctx.label.name + toolchain.binary_ext,
458-
)
459-
else:
460-
output_hash = determine_output_hash(crate_root, ctx.label)
461-
output = ctx.actions.declare_file(
462-
"test-%s/%s%s" % (
463-
output_hash,
464-
ctx.label.name,
465-
toolchain.binary_ext,
466-
),
467-
)
445+
output_hash = determine_output_hash(crate_root, ctx.label)
446+
output = ctx.actions.declare_file(
447+
ctx.label.name + toolchain.binary_ext,
448+
)
468449

469450
rust_metadata = None
470451
rustc_rmeta_output = None
@@ -513,6 +494,7 @@ def _rust_test_impl(ctx):
513494
attr = ctx.attr,
514495
toolchain = toolchain,
515496
crate_info_dict = crate_info_dict,
497+
output_hash = output_hash,
516498
rust_flags = get_rust_test_flags(ctx.attr),
517499
skip_expanding_rustc_env = True,
518500
)

rust/private/rustc.bzl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,13 +1053,13 @@ def construct_arguments(
10531053

10541054
rustc_flags.add(error_format, format = "--error-format=%s")
10551055

1056-
# Mangle symbols to disambiguate crates with the same name. This could
1057-
# happen only for non-final artifacts where we compute an output_hash,
1058-
# e.g., rust_library.
1056+
# Mangle symbols to disambiguate crates with the same name. Used for
1057+
# rust_library (multiple versions of a crate) and rust_test (shares a
1058+
# crate name with the underlying binary/library it tests).
10591059
#
1060-
# For "final" artifacts and ones intended for distribution outside of
1061-
# Bazel, such as rust_binary, rust_static_library and rust_shared_library,
1062-
# where output_hash is None we don't need to add these flags.
1060+
# For final artifacts intended for distribution outside of Bazel, such as
1061+
# rust_binary, rust_static_library and rust_shared_library, output_hash
1062+
# is None and these flags are not added.
10631063
if output_hash:
10641064
rustc_flags.add(output_hash, format = "--codegen=metadata=-%s")
10651065
rustc_flags.add(output_hash, format = "--codegen=extra-filename=-%s")
@@ -1468,10 +1468,13 @@ def rustc_compile_action(
14681468
outputs = [crate_info.output]
14691469

14701470
# The `.o` output file, only used for linking via cc_common.link.
1471+
# When output_hash is set (e.g. for rust_test targets), include it in the
1472+
# filename to avoid collisions with other targets sharing the same crate name.
14711473
output_o = None
14721474
if experimental_use_cc_common_link:
14731475
obj_ext = ".o"
1474-
output_o = ctx.actions.declare_file(crate_info.name + obj_ext, sibling = crate_info.output)
1476+
obj_basename = crate_info.name + ("-%s" % output_hash if output_hash else "")
1477+
output_o = ctx.actions.declare_file(obj_basename + obj_ext, sibling = crate_info.output)
14751478
outputs = [output_o]
14761479

14771480
# For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the

rust/settings/BUILD.bazel

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ load(
2626
"extra_rustc_flag",
2727
"extra_rustc_flags",
2828
"incompatible_change_clippy_error_format",
29-
"incompatible_change_rust_test_compilation_output_directory",
3029
"incompatible_do_not_include_data_in_compile_data",
3130
"lto",
3231
"no_std",
@@ -110,8 +109,6 @@ extra_rustc_flags()
110109

111110
incompatible_change_clippy_error_format()
112111

113-
incompatible_change_rust_test_compilation_output_directory()
114-
115112
incompatible_do_not_include_data_in_compile_data()
116113

117114
lto()

rust/settings/settings.bzl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,6 @@ def toolchain_generated_sysroot():
258258
visibility = ["//visibility:public"],
259259
)
260260

261-
# buildifier: disable=unnamed-macro
262-
def incompatible_change_rust_test_compilation_output_directory():
263-
"""A flag to put rust_test compilation outputs in the same directory as the rust_library compilation outputs.
264-
"""
265-
incompatible_flag(
266-
name = "incompatible_change_rust_test_compilation_output_directory",
267-
build_setting_default = False,
268-
issue = "https://github.com/bazelbuild/rules_rust/issues/2827",
269-
)
270-
271261
def experimental_link_std_dylib():
272262
"""A flag to control whether to link libstd dynamically."""
273263
bool_flag(

rust/toolchain.bzl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,6 @@ def _rust_toolchain_impl(ctx):
621621
_experimental_use_cc_common_link = _experimental_use_cc_common_link(ctx),
622622
_experimental_use_global_allocator = experimental_use_global_allocator,
623623
_experimental_use_coverage_metadata_files = ctx.attr._experimental_use_coverage_metadata_files[BuildSettingInfo].value,
624-
_incompatible_change_rust_test_compilation_output_directory = ctx.attr._incompatible_change_rust_test_compilation_output_directory[IncompatibleFlagInfo].enabled,
625624
_toolchain_generated_sysroot = ctx.attr._toolchain_generated_sysroot[BuildSettingInfo].value,
626625
_incompatible_do_not_include_data_in_compile_data = ctx.attr._incompatible_do_not_include_data_in_compile_data[IncompatibleFlagInfo].enabled,
627626
_no_std = no_std,
@@ -863,9 +862,6 @@ rust_toolchain = rule(
863862
"This flag is only relevant when used together with --@rules_rust//rust/settings:experimental_use_global_allocator."
864863
),
865864
),
866-
"_incompatible_change_rust_test_compilation_output_directory": attr.label(
867-
default = Label("//rust/settings:incompatible_change_rust_test_compilation_output_directory"),
868-
),
869865
"_incompatible_do_not_include_data_in_compile_data": attr.label(
870866
default = Label("//rust/settings:incompatible_do_not_include_data_in_compile_data"),
871867
doc = "Label to a boolean build setting that controls whether to include data files in compile_data.",

test/unit/linkstamps/linkstamps_test.bzl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
44
load("@rules_cc//cc:defs.bzl", "cc_library")
55
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
6-
load("//rust:defs.bzl", "rust_binary", "rust_common", "rust_library", "rust_test")
6+
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
77
load("//test/unit:common.bzl", "assert_action_mnemonic")
88

99
def _is_running_on_linux(ctx):
@@ -25,12 +25,9 @@ def _supports_linkstamps_test(ctx):
2525
linkstamp_out = linkstamp_action.outputs.to_list()[0]
2626
asserts.equals(env, linkstamp_out.basename, "linkstamp.o")
2727
tut_out = tut.files.to_list()[0]
28-
is_test = tut[rust_common.crate_info].is_test
2928
workspace_prefix = _get_workspace_prefix(ctx)
3029

31-
# Rust compilation outputs coming from a test are put in test-{hash} directory
32-
# which we need to remove in order to obtain the linkstamp file path.
33-
dirname = "/".join(tut_out.dirname.split("/")[:-1]) if is_test else tut_out.dirname
30+
dirname = tut_out.dirname
3431
expected_linkstamp_path = dirname + "/_objs/" + tut_out.basename + workspace_prefix + "/test/unit/linkstamps/linkstamp.o"
3532
asserts.equals(
3633
env,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
load(":codegen_disambiguation_test.bzl", "codegen_disambiguation_test_suite")
2+
3+
############################ UNIT TESTS #############################
4+
codegen_disambiguation_test_suite(name = "codegen_disambiguation_test_suite")
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Tests that rust_test targets receive codegen disambiguation flags.
2+
3+
rust_test targets pass --codegen=metadata and --codegen=extra-filename to rustc
4+
so that intermediate compilation artifacts (.o, .d files) get unique names. This
5+
prevents collisions with rust_binary or rust_library targets that share the same
6+
crate name, which would otherwise cause link failures on non-sandboxed builds
7+
(e.g. Windows or --spawn_strategy=standalone). See
8+
https://github.com/bazelbuild/rules_rust/pull/1434 for the original issue.
9+
10+
rustc flag documentation:
11+
https://doc.rust-lang.org/rustc/codegen-options/index.html#metadata
12+
https://doc.rust-lang.org/rustc/codegen-options/index.html#extra-filename
13+
"""
14+
15+
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
16+
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
17+
load("//test/unit:common.bzl", "assert_argv_contains_prefix")
18+
19+
def _codegen_disambiguation_test_impl(ctx):
20+
env = analysistest.begin(ctx)
21+
tut = analysistest.target_under_test(env)
22+
assert_argv_contains_prefix(env, tut.actions[0], "--codegen=metadata=-")
23+
assert_argv_contains_prefix(env, tut.actions[0], "--codegen=extra-filename=-")
24+
return analysistest.end(env)
25+
26+
codegen_disambiguation_test = analysistest.make(
27+
_codegen_disambiguation_test_impl,
28+
)
29+
30+
def _codegen_disambiguation_targets():
31+
rust_binary(
32+
name = "my_binary",
33+
srcs = ["foo.rs"],
34+
edition = "2018",
35+
)
36+
37+
rust_library(
38+
name = "my_library",
39+
srcs = ["foo.rs"],
40+
edition = "2018",
41+
)
42+
43+
rust_test(
44+
name = "my_test_with_srcs",
45+
srcs = ["foo.rs"],
46+
edition = "2018",
47+
)
48+
49+
codegen_disambiguation_test(
50+
name = "rust_test_srcs_codegen_disambiguation_test",
51+
target_under_test = ":my_test_with_srcs",
52+
)
53+
54+
rust_test(
55+
name = "my_test_with_crate_from_bin",
56+
crate = "my_binary",
57+
edition = "2018",
58+
)
59+
60+
codegen_disambiguation_test(
61+
name = "rust_test_crate_from_bin_codegen_disambiguation_test",
62+
target_under_test = ":my_test_with_crate_from_bin",
63+
)
64+
65+
rust_test(
66+
name = "my_test_with_crate_from_lib",
67+
crate = "my_library",
68+
edition = "2018",
69+
)
70+
71+
codegen_disambiguation_test(
72+
name = "rust_test_crate_from_lib_codegen_disambiguation_test",
73+
target_under_test = ":my_test_with_crate_from_lib",
74+
)
75+
76+
def codegen_disambiguation_test_suite(name):
77+
"""Entry-point macro called from the BUILD file.
78+
79+
Args:
80+
name: Name of the macro.
81+
"""
82+
83+
_codegen_disambiguation_targets()
84+
85+
native.test_suite(
86+
name = name,
87+
tests = [
88+
":rust_test_srcs_codegen_disambiguation_test",
89+
":rust_test_crate_from_bin_codegen_disambiguation_test",
90+
":rust_test_crate_from_lib_codegen_disambiguation_test",
91+
],
92+
)
File renamed without changes.

0 commit comments

Comments
 (0)