Skip to content

Commit 23c95ab

Browse files
committed
wip - implement single repo per wheel per hub
remove incorrect code written by AI
1 parent 20db956 commit 23c95ab

10 files changed

Lines changed: 308 additions & 21 deletions
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Single dep for spoke repos per `whl_name`.
2+
3+
The goal is to have `whl_library` instances from wheels (where we pass `url` for a URL or we pass
4+
a `whl_file` label which points to an actual wheel) to be reused across different python versions
5+
and across different configurations where different extras are requested. This means that
6+
if the user is using the same wheel in 2 different configurations, the wheel is reused, because
7+
the extracted contents should not differ. This could be important for:
8+
* platform-specific wheels for custom platforms defined by the user. Otherwise the
9+
platform-specific wheel behaviour should remain unchanged.
10+
* cross-platform wheels for are the main affected item here.
11+
12+
The code should be gated by a feature flag, which can be flipped to be enabled using an
13+
environmental variable via
14+
15+
The file to modify `python/private/internal_config_repo.bzl`. Use the
16+
`RULES_PYTHON_WHL_LIBRARY_OPTIMIZED=1` to enable the feature. Add it to the
17+
`docs/readthedocs_build.sh` file so that we are exercising the code. And document this in
18+
the `docs/environment-variables.md` file.
19+
If the flag is off, then it is equivalent to the current
20+
`main` branch behaviour, if it is on, then it is equivalent to turning all of the behaviour
21+
implemented here on.
22+
23+
Since this is a refactor for users not using private APIs, the env variable will be switched/removed
24+
later together with old code.
25+
26+
All of the code should be written in a TDD style where we add a failing test for a feature and then
27+
we implement the new code.
28+
29+
## Change the naming of the spoke repositories:
30+
31+
> The hub repositories should be named `<hub_name>_<wheel_name_suffix>.
32+
33+
The names should be changed:
34+
* from `+pip+dev_pip_314_requests_py3_none_any_2a0d60c1_linux_x86_64_linux_x86_64_freethreaded`
35+
to `+pip+dev_pip_requests_py3_none_any_2a0d60c1`
36+
* from `+pip+dev_pip_314_roman_numerals_py3_none_any_647ba99c`
37+
to `+pip+dev_pip_roman_numerals_py3_none_any_647ba99c`
38+
* from `+pip+dev_pip_314_markupsafe_cp314_cp314t_manylinux_2_17_x86_64_fed51ac4`
39+
to `+pip+dev_pip_markupsafe_cp314_cp314t_manylinux_2_17_x86_64_fed51ac4`
40+
41+
So the naming convention after the change is:
42+
`<prefix>_<name>_<py_tag>_<abi_tag>_<platform_tag>_<sha256[:8]>`
43+
44+
Where `<prefix>` is the hub repository name and the rest of the segments come from the wheel name
45+
itself.
46+
47+
The sdist building `whl_library` instances should change the naming to:
48+
`<prefix>_<name>_<sha256[:8]>_<rules_python_target_platform>`
49+
50+
The file to modify `python/private/pypi/whl_repo_name.bzl`
51+
52+
## Change the `whl_library_targets`
53+
54+
Using the `METADATA` file that is parsed from `whl_metadata` function where the `Provides-Extra` is
55+
retrieved from the file. Right now we generate a single target which includes all of the extras that
56+
are required. Instead do the following target generation:
57+
* `pkg` (no extras) - target with no extras, this is the main target that includes the python sources,
58+
the other targets just include extra dependencies.
59+
* `pkg__extra` - target for each extra in the provides-extra list. If the target depends on
60+
`self[another_extra]`, then explode the nodes so that the target only depends on `pkg` target +
61+
extra dependencies. Use `py_library` for this. Also be smart about generating targets:
62+
- If the only extra is in the env marker, but the dependency is not in the hub repo, then skip
63+
generating the whole `pkg__extra` target.
64+
- Propose any extra ideas here.
65+
66+
Related files:
67+
* `python/private/pypi/whl_library.bzl`
68+
* `python/private/pypi/whl_metadata.bzl`
69+
* `python/private/pypi/whl_library_targets.bzl`
70+
71+
## Change `hub_builder` and `hub_repository`
72+
73+
Instead of passing the `requirement[extra1,extra2]` to the `whl_library`, pass it to the
74+
`hub_repository` via `whl_config_setting` so that the `render_pkg_aliases` is using the information to alias to the right
75+
extra target based on what is requested. This should retain the behaviour where different target
76+
platforms are allowed to target `foo[baz]` and `foo[bar]` and we select the
77+
`@dev_pip_spoke_repo//:pkg__baz` and `@dev_pip_spoke_repo//:pkg__bar` appropriately.
78+
79+
We should also generate extra targets here:
80+
* `pkg` should point to the target with all specified extras as passed to the `hub_repository`. This is to keep backwards compatibility with how it used to be done
81+
previously. This also keeps compatibility with `rules_pycross`. Add this as a comment
82+
when doing this. The spoke repos should not be used directly, so this difference in
83+
behaviour is OK.
84+
This reuses the targets declared in the hub repository described below.
85+
It is backed by a `py_library` target if there are multiple extras, or an `alias` if it
86+
is only a single extra. It is the same target as described below.
87+
* `pkg[]` should point to the target in the spoke without extras
88+
* `pkg[extra]` should point to the target in the spoke with particular extras. Do this for each
89+
provided extra. So for `requirements[extra1,extra2]` passed to the hub repo, 2 extra targets will
90+
be created. If the extras have not been specified for certain platforms via the
91+
`whl_config_setting`, then the `select` statement should raise an `error` for no match.
92+
Document the mapping explicitly: hub `pkg[]` → spoke `pkg`, hub `pkg[extra]` → spoke `pkg__extra`.
93+
The fact that some of the targets in the spoke repos are unreachable is intentional - this is
94+
because the hub repository contents are created before the `whl_library` downloads the whl and
95+
inspects the METADATA.
96+
The reverse direction is not a concern because the requirements file locking results in
97+
consistent file. If something like this happens, then we should provide a message to the
98+
user that something went wrong and that they should create a ticket in rules_python bug
99+
tracker.
100+
* If there is `requirement[extra1,extra2]` passed, that means that we should create a special alias
101+
that includes both of the targets at once. For this use `py_library` instead of `alias` and pass
102+
`:pkg[extra1]` and `:pkg[extra2]` to the `deps` of the `py_library`. This won't cause
103+
a failure because for a particular platform that is requested this should work by
104+
construction, because the dependency targets will be also defined for the particular platform.
105+
106+
* If the `requirement` is passed without any extras, then hub `pkg` should alias to `pkg[]`.
107+
* If the target is `py_library`, then we should name it without `[]` to avoid any aspects traversing
108+
`py_library` targets changing behaviour. Only alias targets can have `[]`.
109+
110+
Related files:
111+
* `python/private/pypi/render_pkg_aliases.bzl`
112+
* `python/private/pypi/hub_builder.bzl`
113+
* `python/private/pypi/hub_repository.bzl`
114+
* `python/private/pypi/whl_config_setting.bzl` - consider extending this struct to specify which
115+
extras are requested for which platform.
116+
117+
## Modify `pip_repository`
118+
119+
Do similar changes to the `WORKSPACE` code to ensure easier maintenance of `whl_library` so that all
120+
code paths to `whl_library` remain consistent.
121+
122+
Implement this by using the information that we retrieve by parsing the `requirements.txt` files
123+
using the `parse_requirements` function. Parse the requirement itself using the existing parser in
124+
the referenced file `pep508_requirement.bzl`.
125+
126+
Related files:
127+
* `python/private/pypi/pip_repository.bzl`
128+
* `python/private/pypi/requirements.bzl.tmpl.workspace`
129+
* `python/private/pypi/pep508_requirement.bzl`
130+
131+
## Modify `unified_hub_repo`
132+
133+
Pass the extras to the `unified_hub_repo` as well so that the extra targets are created. The target
134+
topology in the unified hub repo should correspond to the hub_repository.
135+
136+
File:
137+
* `python/private/pypi/unified_hub_repo.bzl`
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Accessing build failures
2+
3+
When looking at the test results and the build sandbox, do not ask permission to access the paths
4+
via the absolute path, instead access them via the convenience symlinks in the root of the git
5+
repository.
6+
7+
Patterns:
8+
* Use `bazel-bin/tests/pypi/hub_builder` to access the test results for the `//tests/pypi/hub_builder`
9+
* Use `bazel-rules_python/external/` to access the external repos fetch or materialized by the
10+
extensions
11+
* Use `bazel-testlogs/tests/pypi/hub_builder/test_simple` to access the logs for the `//tests/pypi/hub_builder:test_simple` test.

docs/readthedocs_build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/usr/bin/env bash
22

3+
# Exercising the whl_library optimized mode to ensure it is working
4+
export RULES_PYTHON_WHL_LIBRARY_OPTIMIZED=1
5+
36
set -eou pipefail
47

58
declare -a extra_env

python/private/internal_config_repo.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ load(":repo_utils.bzl", "repo_utils")
2424
_ENABLE_DEPRECATION_WARNINGS_ENVVAR_NAME = "RULES_PYTHON_DEPRECATION_WARNINGS"
2525
_ENABLE_DEPRECATION_WARNINGS_DEFAULT = "0"
2626

27+
_WHL_LIBRARY_OPTIMIZED_ENVVAR_NAME = "RULES_PYTHON_WHL_LIBRARY_OPTIMIZED"
28+
_WHL_LIBRARY_OPTIMIZED_DEFAULT = "0"
29+
2730
_CONFIG_TEMPLATE = """
2831
config = struct(
2932
build_python_zip_default = {build_python_zip_default},
3033
supports_whl_extraction = {supports_whl_extraction},
3134
enable_pystar = True,
3235
enable_deprecation_warnings = {enable_deprecation_warnings},
3336
extract_needs_chmod = {extract_needs_chmod},
37+
whl_library_optimized = {whl_library_optimized},
3438
bazel_8_or_later = {bazel_8_or_later},
3539
bazel_9_or_later = {bazel_9_or_later},
3640
bazel_10_or_later = {bazel_10_or_later},
@@ -104,6 +108,7 @@ def _internal_config_repo_impl(rctx):
104108
rctx.file("rules_python_config.bzl", _CONFIG_TEMPLATE.format(
105109
build_python_zip_default = repo_utils.get_platforms_os_name(rctx) == "windows",
106110
enable_deprecation_warnings = _bool_from_environ(rctx, _ENABLE_DEPRECATION_WARNINGS_ENVVAR_NAME, _ENABLE_DEPRECATION_WARNINGS_DEFAULT),
111+
whl_library_optimized = _bool_from_environ(rctx, _WHL_LIBRARY_OPTIMIZED_ENVVAR_NAME, _WHL_LIBRARY_OPTIMIZED_DEFAULT),
107112
builtin_py_info_symbol = builtin_py_info_symbol,
108113
builtin_py_runtime_info_symbol = builtin_py_runtime_info_symbol,
109114
supports_whl_extraction = str(supports_whl_extraction),

python/private/pypi/generate_whl_library_build_bazel.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ _RENDER = {
2727
"extras": render.list,
2828
"group_deps": render.list,
2929
"include": str,
30+
"provides_extra": render.list,
3031
"requires_dist": render.list,
3132
"srcs_exclude": render.list,
3233
"tags": render.list,

python/private/pypi/hub_builder.bzl

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ load(":attrs.bzl", "use_isolated")
1111
load(":parse_requirements.bzl", "parse_requirements")
1212
load(":pep508_env.bzl", "env")
1313
load(":pep508_evaluate.bzl", "evaluate")
14+
load(":pep508_requirement.bzl", "requirement")
1415
load(":python_tag.bzl", "python_tag")
1516
load(":requirements_files_by_platform.bzl", "requirements_files_by_platform")
1617
load(":whl_config_setting.bzl", "whl_config_setting")
@@ -308,13 +309,10 @@ def _add_whl_library(self, *, python_version, whl, repo):
308309
# disallow building from sdist.
309310
return
310311

311-
# TODO @aignas 2025-06-29: we should not need the version in the repo_name if
312-
# we are using pipstar and we are downloading the wheel using the downloader
313-
#
314-
# However, for that we should first have a different way to reference closures with
315-
# extras. For example, if some package depends on `foo[extra]` and another depends on
316-
# `foo`, we should have 2 py_library targets.
317-
repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name)
312+
if self._config.whl_library_optimized:
313+
repo_name = "{}_{}".format(self.name, repo.repo_name)
314+
else:
315+
repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name)
318316

319317
if repo_name in self._whl_libraries:
320318
diff = _diff_dict(self._whl_libraries[repo_name], repo.args)
@@ -538,6 +536,7 @@ def _create_whl_repos(
538536
)
539537
for src in whl.srcs:
540538
repo = _whl_repo(
539+
self,
541540
src = src,
542541
index_url = whl.index_url,
543542
whl_library_args = whl_library_args,
@@ -549,6 +548,7 @@ def _create_whl_repos(
549548
is_multiple_versions = whl.is_multiple_versions,
550549
interpreter = interpreter,
551550
enable_pipstar_extract = enable_pipstar_extract,
551+
optimized = self._config.whl_library_optimized,
552552
)
553553
_add_whl_library(
554554
self,
@@ -612,6 +612,7 @@ def _whl_library_args(self, *, whl, whl_modifications):
612612
return whl_library_args
613613

614614
def _whl_repo(
615+
self,
615616
*,
616617
src,
617618
whl_library_args,
@@ -623,9 +624,21 @@ def _whl_repo(
623624
python_version,
624625
use_downloader,
625626
interpreter,
626-
enable_pipstar_extract = False):
627+
enable_pipstar_extract = False,
628+
optimized = False):
627629
args = dict(whl_library_args)
628-
args["requirement"] = src.requirement_line
630+
if self._config.whl_library_optimized:
631+
line = requirement(src.requirement_line)
632+
extras = line.extras
633+
634+
# Strip extras from the requirement for whls in optimized mode so that
635+
# the wheel can be reused across different configurations requesting
636+
# different extras.
637+
args["requirement"] = line.name
638+
else:
639+
args["requirement"] = src.requirement_line
640+
extras = []
641+
629642
is_whl = src.filename.endswith(".whl")
630643

631644
if src.extra_pip_args and not is_whl:
@@ -658,6 +671,7 @@ def _whl_repo(
658671
config_setting = whl_config_setting(
659672
version = python_version,
660673
target_platforms = target_platforms or None,
674+
extras = extras,
661675
),
662676
)
663677

@@ -678,14 +692,15 @@ def _whl_repo(
678692
# TODO @aignas 2025-11-02: once we have pipstar enabled we can add extra
679693
# targets to each hub for each extra combination and solve this more cleanly as opposed to
680694
# duplicating whl_library repositories.
681-
target_platforms = src.target_platforms if is_multiple_versions else []
695+
target_platforms = src.target_platforms if (is_multiple_versions and not optimized) else []
682696

683697
return struct(
684-
repo_name = whl_repo_name(src.filename, src.sha256, *target_platforms),
698+
repo_name = whl_repo_name(src.filename, src.sha256, target_platforms = target_platforms, optimized = optimized),
685699
args = args,
686700
config_setting = whl_config_setting(
687701
version = python_version,
688702
target_platforms = src.target_platforms,
703+
extras = extras,
689704
),
690705
)
691706

python/private/pypi/hub_repository.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,6 @@ def _whl_config_setting_dict(a):
146146
ret["target_platforms"] = a.target_platforms
147147
if a.version:
148148
ret["version"] = a.version
149+
if a.extras:
150+
ret["extras"] = a.extras
149151
return ret

python/private/pypi/whl_config_setting.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"A small function to create an alias for a whl distribution"
1616

17-
def whl_config_setting(*, version = None, target_platforms = None):
17+
def whl_config_setting(*, version = None, target_platforms = None, extras = None):
1818
"""The bzl_packages value used by by the render_pkg_aliases function.
1919
2020
This contains the minimum amount of information required to generate correct
@@ -27,6 +27,8 @@ def whl_config_setting(*, version = None, target_platforms = None):
2727
is no match found during a select.
2828
target_platforms: {type}`list[str] | None` the list of target_platforms for this
2929
distribution.
30+
extras: {type}`list[str] | None` the list of extras for this particular target platform to
31+
enable.
3032
3133
Returns:
3234
a struct with the validated and parsed values.
@@ -55,4 +57,5 @@ def whl_config_setting(*, version = None, target_platforms = None):
5557
# Make the struct hashable
5658
target_platforms = tuple(target_platforms) if target_platforms else None,
5759
version = version,
60+
extras = extras or None,
5861
)

0 commit comments

Comments
 (0)