Skip to content

Commit b8a2952

Browse files
gregmagolanclaude
andauthored
feat(whl_install): expose install_dir via OutputGroupInfo (#926)
## Problem After #907 removed `install_dir` from `DefaultInfo.files`, consumers that need to access files within the wheel directory directly (e.g. extracting a non-console-script binary like `py-spy`) have no way to reference the install directory from a genrule `srcs`. ## Fix Add `OutputGroupInfo(install_dir = depset([install_dir]))` to the `whl_install` rule's return value. Consumers can access the directory via a `filegroup` with `output_group = "install_dir"`: ```python filegroup( name = "py_spy_install_dir", srcs = ["@pip//py_spy:install"], output_group = "install_dir", ) genrule( name = "py_spy_cli", srcs = [":py_spy_install_dir"], outs = ["py-spy"], cmd = "cp $(execpath :py_spy_install_dir)/bin/py-spy $@", ) ``` This keeps `DefaultInfo.files` empty (preserving the improvement from #907) while still making the install directory reachable for the rare cases that need it. ## Test plan - [x] Existing e2e suite passes - [x] New e2e test case `uv-whl-install-output-group`: installs `iniconfig` via uv, accesses the install dir through `filegroup(output_group = "install_dir")`, uses it as a `genrule` srcs, and asserts the listing contains expected package files --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f04d69a commit b8a2952

6 files changed

Lines changed: 112 additions & 0 deletions

File tree

e2e/MODULE.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,18 @@ uv.project(
249249
)
250250
# }}}
251251

252+
# For cases/uv-whl-install-output-group
253+
# Verify that whl_install exposes install_dir via OutputGroupInfo so that a
254+
# filegroup with output_group = "install_dir" makes the wheel directory
255+
# accessible to downstream genrules.
256+
# {{{
257+
uv.project(
258+
hub_name = "pypi",
259+
lock = "//cases/uv-whl-install-output-group:uv.lock",
260+
pyproject = "//cases/uv-whl-install-output-group:pyproject.toml",
261+
)
262+
# }}}
263+
252264
# For cases/uv-include-group (PEP 735 include-group syntax)
253265
# {{{
254266
uv.project(
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Regression test: whl_install must expose install_dir via OutputGroupInfo.
2+
#
3+
# Since #907 removed install_dir from DefaultInfo.files, consumers that need to
4+
# access files from the wheel directory directly must use a filegroup with
5+
# output_group = "install_dir". This test exercises that pattern end-to-end.
6+
7+
load("@aspect_rules_py//py/unstable:defs.bzl", "py_venv_test")
8+
9+
# Access the install dir via the install_dir output group rather than
10+
# DefaultInfo.files (which is intentionally empty since #907).
11+
#
12+
# This filegroup is placed in py_venv_test's data (not a genrule srcs) so that
13+
# the venv transition applies and @pypi//iniconfig's hub select resolves.
14+
filegroup(
15+
name = "iniconfig_install_dir",
16+
srcs = ["@pypi//iniconfig"],
17+
output_group = "install_dir",
18+
)
19+
20+
py_venv_test(
21+
name = "test",
22+
srcs = ["test.py"],
23+
data = [":iniconfig_install_dir"],
24+
main = "test.py",
25+
python_version = "3.11",
26+
venv = "uv-whl-install-output-group",
27+
deps = ["@pypi//iniconfig"],
28+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[project]
2+
name = "uv-whl-install-output-group"
3+
version = "0.0.0"
4+
requires-python = ">=3.11"
5+
dependencies = [
6+
"iniconfig",
7+
]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Test that whl_install exposes install_dir via OutputGroupInfo.
2+
3+
filegroup(output_group="install_dir") is placed in py_venv_test's data so the
4+
venv transition applies. If OutputGroupInfo.install_dir is not exposed, the
5+
filegroup produces no files and the TreeArtifact is absent from TEST_SRCDIR.
6+
"""
7+
8+
import os
9+
import unittest
10+
11+
12+
class WhlInstallOutputGroupTest(unittest.TestCase):
13+
def test_install_dir_accessible_via_output_group(self):
14+
srcdir = os.environ["TEST_SRCDIR"]
15+
# Find any .py file under the iniconfig install directory. The tree
16+
# artifact is placed somewhere under TEST_SRCDIR by Bazel's runfiles.
17+
found = []
18+
for root, _dirs, files in os.walk(srcdir):
19+
for f in files:
20+
path = os.path.join(root, f)
21+
if "iniconfig" in path and f.endswith(".py"):
22+
found.append(path)
23+
24+
self.assertGreater(
25+
len(found),
26+
0,
27+
"No iniconfig .py files found under TEST_SRCDIR — "
28+
"OutputGroupInfo.install_dir was not exposed or not propagated "
29+
"through filegroup(output_group='install_dir')\n"
30+
f"TEST_SRCDIR={srcdir}",
31+
)
32+
33+
34+
if __name__ == "__main__":
35+
unittest.main()

e2e/cases/uv-whl-install-output-group/uv.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

uv/private/whl_install/rule.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def _whl_install(ctx):
7474
install_dir,
7575
]),
7676
),
77+
OutputGroupInfo(
78+
# Expose install_dir for consumers that need to access files from
79+
# the wheel directory directly (e.g. extracting non-console-script
80+
# binaries via a filegroup with output_group = "install_dir").
81+
install_dir = depset([install_dir]),
82+
),
7783
PyInfo(
7884
transitive_sources = depset([
7985
install_dir,
@@ -133,6 +139,7 @@ lighter weight since the toolchain's files aren't inputs.
133139
],
134140
provides = [
135141
DefaultInfo,
142+
OutputGroupInfo,
136143
PyInfo,
137144
],
138145
)

0 commit comments

Comments
 (0)