Skip to content

Commit ffa3ede

Browse files
authored
Map all generated input roots into legacy doctest runfiles (#4271)
Legacy doctests run from a runfiles directory, but the launcher only strips output roots found through crate and C++ library providers. A generated rustc environment file built in an execution configuration retains a `bazel-out/...` path and fails before rustdoc starts. Collect the nonempty roots of every declared action input instead. This also covers generated compiler/linker tools and keeps source paths intact. The new regression executes a doctest using an environment file forwarded through an execution transition; it fails with a missing-file error on main and passes with this change. Validation on macOS with Bazel 9.2.0: 17 owning tests pass on this standalone branch. This extends the C++ runtime-root handling from #4220. Windows execution was not tested locally. Closes #4288.
1 parent bb276bd commit ffa3ede

4 files changed

Lines changed: 58 additions & 52 deletions

File tree

‎rust/private/rustdoc_test.bzl‎

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,7 @@ load("//rust/private:providers.bzl", "CrateInfo")
2020
load("//rust/private:rustdoc.bzl", "rustdoc_compile_action")
2121
load("//rust/private:utils.bzl", "dedent", "find_toolchain", "transform_deps")
2222

23-
def _collect_library_roots(roots, deps):
24-
for dep in deps.to_list():
25-
for lib in dep.libraries:
26-
for artifact in [lib.static_library, lib.pic_static_library]:
27-
if artifact:
28-
roots.append(artifact.root.path)
29-
for input in dep.additional_inputs:
30-
roots.append(input.root.path)
31-
32-
def _construct_writer_arguments(ctx, test_runner, opt_test_params, action, crate_info):
23+
def _construct_writer_arguments(ctx, test_runner, opt_test_params, action):
3324
"""Construct arguments and environment variables specific to `rustdoc_test_writer`.
3425
3526
This is largely solving for the fact that tests run from a runfiles directory
@@ -41,7 +32,6 @@ def _construct_writer_arguments(ctx, test_runner, opt_test_params, action, crate
4132
test_runner (File): The test_runner output file declared by `rustdoc_test`.
4233
opt_test_params (File): An output file we can optionally use to store params for `rustdoc`.
4334
action (struct): Action arguments generated by `rustdoc_compile_action`.
44-
crate_info (CrateInfo): The provider of the crate who's docs are being tested.
4535
4636
Returns:
4737
tuple: A tuple of `rustdoc_test_writer` specific inputs
@@ -65,46 +55,11 @@ def _construct_writer_arguments(ctx, test_runner, opt_test_params, action, crate
6555
for var in action.env.keys():
6656
writer_args.add("--action_env={}".format(var))
6757

68-
# Since the test runner will be running from a runfiles directory, the
69-
# paths originally generated for the build action will not map to any
70-
# files. To ensure rustdoc can find the appropriate dependencies, the
71-
# file roots are identified and tracked for each dependency so it can be
72-
# stripped from the test runner.
73-
74-
# Collect and dedupe all of the file roots in a list before appending
75-
# them to args to prevent generating a large amount of identical args
76-
roots = [crate_info.output.root.path]
77-
for dep in crate_info.deps.to_list() + crate_info.proc_macro_deps.to_list():
78-
dep_crate_info = getattr(dep, "crate_info", None)
79-
dep_dep_info = getattr(dep, "dep_info", None)
80-
dep_cc_info = getattr(dep, "cc_info", None)
81-
if dep_crate_info:
82-
roots.append(dep_crate_info.output.root.path)
83-
if dep_dep_info:
84-
for direct_dep in dep_dep_info.direct_crates.to_list():
85-
roots.append(direct_dep.dep.output.root.path)
86-
for transitive_dep in dep_dep_info.transitive_crates.to_list():
87-
roots.append(transitive_dep.output.root.path)
88-
_collect_library_roots(roots, dep_dep_info.transitive_noncrates)
89-
if dep_cc_info:
90-
_collect_library_roots(roots, dep_cc_info.linking_context.linker_inputs)
91-
92-
# The cc_toolchain runtime libs (see rustdoc.bzl) are built in their own
93-
# configuration, so their root differs from every crate root collected
94-
# above. Without stripping it too, the `-Lnative=` search path rustc emits
95-
# for them stays an execroot path that does not exist under runfiles, and
96-
# the linker reports "unable to find library".
97-
#
98-
# Source files have an empty root, and they need no stripping: their
99-
# `-Lnative=` path is already workspace-relative. Skip them -- an empty
100-
# root would add `--strip_substring=/`, and the writer applies these as
101-
# plain string replacements, so that would delete every `/` in every
102-
# argument.
103-
for lib in action.static_runtime_libs:
104-
if lib.root.path:
105-
roots.append(lib.root.path)
106-
107-
writer_args.add_all(roots, format_each = "--strip_substring=%s/", uniquify = True)
58+
# Every declared input is staged in runfiles. Strip each generated root,
59+
# including compiler tools built in an execution configuration. Empty roots
60+
# are source files: stripping "/" would corrupt every path in the command.
61+
roots = {file.root.path: True for file in action.inputs.to_list() if file.root.path}
62+
writer_args.add_all(roots.keys(), format_each = "--strip_substring=%s/")
10863

10964
# Indicate that the rustdoc_test args are over.
11065
writer_args.add("--")
@@ -287,7 +242,6 @@ def _legacy_rust_doc_test_impl(ctx, toolchain, crate_info):
287242
test_runner = test_runner,
288243
opt_test_params = opt_test_params,
289244
action = action,
290-
crate_info = crate_info,
291245
)
292246

293247
# Allow writer environment variables to override those from the action.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
2+
load("//rust:defs.bzl", "rust_doc_test", "rust_library")
3+
load(":input.bzl", "exec_file")
4+
5+
rust_doc_test(
6+
name = "generated_environment_test",
7+
crate = ":lib",
8+
# Legacy doctests require directory runfiles on Windows (#1156).
9+
target_compatible_with = select({
10+
"@platforms//os:windows": ["@platforms//:incompatible"],
11+
"//conditions:default": [],
12+
}),
13+
)
14+
15+
rust_library(
16+
name = "lib",
17+
srcs = ["lib.rs"],
18+
edition = "2021",
19+
rustc_env_files = [":exec_environment"],
20+
)
21+
22+
exec_file(
23+
name = "exec_environment",
24+
src = ":environment",
25+
)
26+
27+
write_file(
28+
name = "environment",
29+
out = "environment.txt",
30+
content = ["GENERATED_VALUE=from the execution configuration"],
31+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""Expose an environment file built in an execution configuration."""
2+
3+
def _exec_file_impl(ctx):
4+
return [DefaultInfo(files = ctx.attr.src[DefaultInfo].files)]
5+
6+
exec_file = rule(
7+
implementation = _exec_file_impl,
8+
attrs = {
9+
"src": attr.label(mandatory = True, cfg = "exec"),
10+
},
11+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! The wrapper must find generated environment files after moving into runfiles.
2+
//!
3+
//! Unlike the runtime libraries covered by #4220, this file has no crate or
4+
//! C++ dependency provider from which the legacy launcher could infer its root.
5+
//!
6+
//! ```
7+
//! assert_eq!(env!("GENERATED_VALUE"), "from the execution configuration");
8+
//! ```
9+
10+
#![warn(rust_2018_idioms)]

0 commit comments

Comments
 (0)