Skip to content

Commit 0dd018b

Browse files
authored
feat(whl_install): enable path mapping for remote cache deduplication (#906)
## Summary Adds `supports-path-mapping: "1"` to the `WhlInstall` action, allowing remote build systems to deduplicate wheel installation actions across configurations that differ only in their config segment (e.g. different `--compilation_mode` values that don't affect installed wheel content). Path mapping requires that file arguments are passed as `File` objects to `ctx.actions.args` rather than `.path` strings — Bazel cannot rewrite string literals when computing the remote cache key. This PR switches all file arguments accordingly: - `install_dir` and `archive` passed as `File` objects via `args.add()` - Patch files switched from `for f in patch_files: args.add("--patch", f.path)` to `args.add_all(patch_files, before_each = "--patch")` - `exec_runtime.interpreter` was already passed as a `File` object Also adds `mnemonic = "WhlInstall"` for clearer identification in build output and remote cache tooling. ## Compatibility `supports-path-mapping` is a no-op unless the user opts in with `--experimental_output_paths=strip`, so this is safe for all existing users. The known Bazel tree-artifact + path-mapping bug (bazelbuild/bazel#23681) was specific to C++ header discovery and was fixed in Bazel 7.4.0. There are no known issues with `declare_directory` outputs in Starlark actions. One edge case to be aware of: Bazel silently produces wrong results when a path-mapped action runs under a local/unsandboxed execution strategy (bazelbuild/bazel#28910). Users who override `--strategy=WhlInstall=local` should not use `--experimental_output_paths=strip`. ## Test plan - [x] Existing e2e suite passes (patching, compile_pyc, crossbuild tests)
1 parent 8efcc57 commit 0dd018b

2 files changed

Lines changed: 30 additions & 14 deletions

File tree

e2e/.bazelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1
88

99
# Configure a default venv
1010
# common --@pypi//venv=say
11+
12+
# Enable path mapping so WhlInstall actions are exercised with
13+
# supports-path-mapping. Actions that declare the execution requirement get
14+
# configuration-stripped cache keys; those that don't still work normally.
15+
build --experimental_output_paths=strip

uv/private/whl_install/rule.bzl

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,32 @@ def _whl_install(ctx):
1111
"install",
1212
)
1313

14-
archive = ctx.attr.src[DefaultInfo].files.to_list()[0]
14+
archive = ctx.file.src
1515

1616
arguments = ctx.actions.args()
17-
arguments.add_all([
18-
"--into",
19-
install_dir.path,
20-
"--wheel",
21-
archive.path,
22-
"--python-version-major",
23-
py_toolchain.interpreter_version_info.major,
24-
"--python-version-minor",
25-
py_toolchain.interpreter_version_info.minor,
26-
])
17+
18+
# Pass File objects (not .path strings) so Bazel can rewrite paths for
19+
# remote-cache deduplication when supports-path-mapping is set.
20+
#
21+
# Both install_dir and archive may be tree artifacts (install_dir is always
22+
# a declare_directory; archive is a tree when src is a directory containing
23+
# a single wheel). Args#add rejects directories
24+
# outright; Args#add_all with expand_directories=False passes the directory
25+
# path itself without enumerating its contents.
26+
# https://bazel.build/versions/7.1.0/rules/lib/builtins/Args#add
27+
# https://bazel.build/versions/7.1.0/rules/lib/builtins/Args#add_all
28+
arguments.add_all([install_dir], expand_directories = False, before_each = "--into")
29+
arguments.add_all([archive], expand_directories = False, before_each = "--wheel")
30+
arguments.add("--python-version-major", py_toolchain.interpreter_version_info.major)
31+
arguments.add("--python-version-minor", py_toolchain.interpreter_version_info.minor)
2732

2833
transitive_inputs = [depset([archive])]
2934

3035
# Patch application (happens before pyc compilation).
3136
patch_files = [f for t in ctx.attr.patches for f in t[DefaultInfo].files.to_list()]
3237
if patch_files:
3338
arguments.add("--patch-strip", str(ctx.attr.patch_strip))
34-
for f in patch_files:
35-
arguments.add("--patch", f.path)
39+
arguments.add_all(patch_files, before_each = "--patch")
3640
transitive_inputs.append(depset(patch_files))
3741

3842
# Optional .pyc pre-compilation (runs after patching).
@@ -52,13 +56,17 @@ def _whl_install(ctx):
5256
# to use unpack in crossbuild scenarios.
5357
unpack = ctx.attr._unpack[platform_common.ToolchainInfo].bin.bin
5458
ctx.actions.run(
59+
mnemonic = "WhlInstall",
5560
executable = unpack,
5661
arguments = [arguments],
5762
inputs = depset(transitive = transitive_inputs),
5863
outputs = [
5964
install_dir,
6065
],
6166
use_default_shell_env = bool(patch_files),
67+
execution_requirements = {
68+
"supports-path-mapping": "1",
69+
},
6270
)
6371

6472
return [
@@ -100,7 +108,10 @@ to bypass some of the platform checks that UV does to enable crossbuilds, and is
100108
lighter weight since the toolchain's files aren't inputs.
101109
""",
102110
attrs = {
103-
"src": attr.label(doc = "The wheel to install, or a tree artifact containing exactly one wheel at its root."),
111+
"src": attr.label(
112+
allow_single_file = True,
113+
doc = "The wheel to install, or a tree artifact containing exactly one wheel at its root.",
114+
),
104115
"patches": attr.label_list(
105116
default = [],
106117
allow_files = [".patch", ".diff"],

0 commit comments

Comments
 (0)