From 0435525ce7bf4e1049fb2a4241ecb18d1ab436b5 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 1 Aug 2026 06:57:26 -0400 Subject: [PATCH] Disable path mapping for Rust linking actions C/C++ toolchain APIs flatten selected linker paths and feature-expanded arguments into strings. With path mapping enabled, generated linker, library-search, resource, and CRT paths retain their real configuration while the execution sandbox uses mapped paths, causing native links to fail. Do not advertise path mapping for actions that link native artifacts, including WebAssembly native dependencies. Retain mapping for Rust libraries, metadata, and ordinary documentation. Represent generated-file paths in compiler environment files as substitution tokens. Resolve each token from its File in the consuming action so shared environment files remain valid for both mapped and unmapped layouts. Assisted-by: Codex --- rust/private/rustc.bzl | 41 ++++++++++++--- rust/private/rustdoc.bzl | 3 +- .../metadata_output_groups.bzl | 7 +++ test/unit/native_deps/native_deps_test.bzl | 4 ++ test/unit/rustdoc/rustdoc_unit_test.bzl | 1 + tools/rust_analyzer/env_file.bzl | 52 ++++++++----------- 6 files changed, 72 insertions(+), 36 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 04818522c7..9ffd66846d 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -918,12 +918,16 @@ def has_location_expansion(values): return False def _args_map_bin_dir(file): - """Extract `bazel-out//bin` from a File whose path lives in the configuration's bin directory. + """Return a generated File's output root in its consuming action's layout. - Evaluated at action-execution time so that Bazel's path mapping (`--experimental_output_paths=strip`) can - rewrite the `` segment to `cfg` before we slice it off. + `File.root.path` provides the root depth, including sibling repositories; + `File.path` carries the configuration selected by the action's path mapper. """ - return "/".join(file.path.split("/", 3)[:3]) + return "/".join(file.path.split("/")[:len(file.root.path.split("/"))]) + +def _args_map_output_configuration(file): + """Return the output configuration in the consuming action's path layout.""" + return _args_map_bin_dir(file).split("/")[-2] def construct_arguments( *, @@ -1053,6 +1057,16 @@ def construct_arguments( for build_env_file in build_env_files: process_wrapper_flags.add("--env-file", build_env_file) + # Source env files have no output configuration to substitute. + if not build_env_file.is_source: + process_wrapper_flags.add_all( + [build_env_file], + before_each = "--subst", + format_each = "rustc_env_file:" + build_env_file.short_path.replace("%", "%25").replace("=", "%3D") + "=%s", + map_each = _args_map_output_configuration, + expand_directories = False, + ) + process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file") all_allowed_unstable_features = [] @@ -1291,8 +1305,14 @@ def construct_arguments( # If linker_type is not explicitly set, infer from which linker is actually being used ld_is_direct_driver = False + # Metadata does not link, so avoid expanding string-valued linker flags. + adds_linker_args = not build_metadata and ( + ("link" in emit and crate_info.type not in ["rlib", "lib"]) or + add_flags_for_binary + ) + # Link! - if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or add_flags_for_binary: + if adds_linker_args: # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc # linker since it won't understand. compilation_mode = ctx.var["COMPILATION_MODE"] @@ -1540,7 +1560,16 @@ def construct_arguments( rustc_path = rustc_path, rustc_flags = rustc_flags, extra_rustc_flags = rust_flags_args, - supports_path_mapping = not target_has_location_expansion, + # Native linker selection and C/C++ feature expansion flatten generated + # paths into strings that Bazel cannot remap. Pure Wasm remains mappable. + supports_path_mapping = not target_has_location_expansion and not ( + adds_linker_args and + (include_link_flags or force_depend_on_objects) and + ( + toolchain.target_arch not in ("wasm32", "wasm64") or + bool(dep_info.transitive_noncrates) + ) + ), all = all_args, ) diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl index 4c407755f9..d0403251f7 100644 --- a/rust/private/rustdoc.bzl +++ b/rust/private/rustdoc.bzl @@ -189,7 +189,8 @@ def rustdoc_compile_action( build_flags_files = build_flags_files, emit = [], remap_path_prefix = None, - add_flags_for_binary = True, + # Documentation does not link; doctest compilation does. + add_flags_for_binary = is_test or force_depend_on_objects, include_link_flags = False, force_depend_on_objects = force_depend_on_objects, skip_expanding_rustc_env = True, diff --git a/test/unit/metadata_output_groups/metadata_output_groups.bzl b/test/unit/metadata_output_groups/metadata_output_groups.bzl index ec89e0f132..6cebfa6e2e 100644 --- a/test/unit/metadata_output_groups/metadata_output_groups.bzl +++ b/test/unit/metadata_output_groups/metadata_output_groups.bzl @@ -23,6 +23,13 @@ def _metadata_output_groups_present_test_impl(ctx): "Expected %s to end with .rustc-output" % rustc_rmeta_output[0], ) + metadata_action = [action for action in tut.actions if action.mnemonic == "RustcMetadata"][0] + asserts.equals(env, [], [ + arg + for arg in metadata_action.argv + if arg.startswith("--codegen=linker=") or arg.startswith("--codegen=link-arg=") + ]) + return analysistest.end(env) def _metadata_output_groups_missing_test_impl(ctx): diff --git a/test/unit/native_deps/native_deps_test.bzl b/test/unit/native_deps/native_deps_test.bzl index 20423203fa..ef1d7cd81b 100644 --- a/test/unit/native_deps/native_deps_test.bzl +++ b/test/unit/native_deps/native_deps_test.bzl @@ -192,6 +192,10 @@ def _bin_has_native_dep_and_alwayslink_test_impl(ctx, use_cc_linker): link_args = _extract_linker_args(action.argv) bin_dir = get_bin_dir_from_action(action) + # Native linking must retain the same output root as its declared output. + output_bin_dir = action.outputs.to_list()[0].path.split("/bin/", 1)[0] + "/bin" + asserts.equals(env, output_bin_dir, bin_dir) + # Validate bin_dir structure (ignoring ST-{hash} suffix from config transitions) _assert_bin_dir_structure(env, ctx, bin_dir, toolchain) diff --git a/test/unit/rustdoc/rustdoc_unit_test.bzl b/test/unit/rustdoc/rustdoc_unit_test.bzl index b0740cb861..42b3dcfb66 100644 --- a/test/unit/rustdoc/rustdoc_unit_test.bzl +++ b/test/unit/rustdoc/rustdoc_unit_test.bzl @@ -41,6 +41,7 @@ def _common_rustdoc_checks(env, tut): # `Rustdoc` actions assert_argv_contains_prefix_not(env, action, "--remap-path-prefix") assert_argv_contains_prefix_not(env, action, "--emit") + assert_argv_contains_prefix_not(env, action, "--codegen=linker=") def _rustdoc_for_lib_test_impl(ctx): env = analysistest.begin(ctx) diff --git a/tools/rust_analyzer/env_file.bzl b/tools/rust_analyzer/env_file.bzl index 5e1f6abcbd..959e56edf5 100644 --- a/tools/rust_analyzer/env_file.bzl +++ b/tools/rust_analyzer/env_file.bzl @@ -1,32 +1,27 @@ -"""A `rustc_env_files`-format rule whose VALUE is a path-mapping-aware file path.""" - -load("@bazel_features//:features.bzl", "bazel_features") - -def _arg_map_pair(value): - key, file = value - return "{}=${{pwd}}/{}".format(key, file.path) +"""A `rustc_env_files`-format rule whose file paths follow the consuming action.""" def _env_file_impl(ctx): out = ctx.actions.declare_file(ctx.label.name + ".rustc_env") - content = ctx.actions.args() - content.set_param_file_format("multiline") - content.add_all( - [(ctx.attr.key, ctx.file.src)], - map_each = _arg_map_pair, - expand_directories = False, - ) - if bazel_features.rules.write_action_has_execution_requirements: - ctx.actions.write( - output = out, - content = content, - execution_requirements = {"supports-path-mapping": ""}, - ) - else: - ctx.actions.write( - output = out, - content = content, + # ':' reserves this namespace; escape '%' first to keep keys injective. + substitution = "rustc_env_file:" + out.short_path.replace("%", "%25").replace("=", "%3D") + source_root = ctx.file.src.root.path + root_components = source_root.split("/") + + # Generated inputs share this rule's configuration; defer that component to + # the consuming action while preserving their repository and output root. + source_path = ( + ctx.file.src.path if ctx.file.src.is_source else "{}/${{{}}}/{}/{}".format( + "/".join(root_components[:-2]), + substitution, + root_components[-1], + ctx.file.src.path[len(source_root) + 1:], ) + ) + ctx.actions.write( + output = out, + content = "{}=${{pwd}}/{}\n".format(ctx.attr.key, source_path), + ) return [DefaultInfo(files = depset([out]))] env_file = rule( @@ -43,11 +38,10 @@ env_file = rule( ), }, doc = """\ -Emit a one-line `KEY=${pwd}/\\n` file suitable for `rust_library`'s -`rustc_env_files` attribute. The path is generated through `Args.add_all`'s -`format_each` so Bazel's path mapping (`--experimental_output_paths=strip`) -rewrites it before the action runs, and the `${pwd}` prefix is later resolved -to the exec_root by `process_wrapper`'s env-block substitution. +Emit a `KEY=${pwd}/\\n` file suitable for `rustc_env_files`. +Generated inputs share this rule's configuration. A File-backed substitution +selects the consuming action's mapped or unmapped configuration while preserving +the input's repository and output root. Pair with a matching `compile_data = [src]` on the consumer crate and use `include_str!(env!("KEY"))` in Rust to embed the file's content at compile