perf(pypi): extract wheels once and reuse - #3856
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements an optimized mode for whl_library (gated by the RULES_PYTHON_WHL_LIBRARY_OPTIMIZED environment variable) to allow wheel reuse across different Python versions by omitting the Python version from spoke repository names, generating per-extra targets, and creating explicit aliases in the hub repository. The code review identified several critical issues with this implementation: static aliases in render_pkg_aliases.bzl break multi-version/multi-platform setups, and iterating over extras_info causes aliases to overwrite each other. Additionally, a naming mismatch in extension.bzl between the unified hub alias and the hub repository package name will result in broken aliases, and skipping pkg__extra target generation when there are no extra dependencies leads to build failures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def _render_extra_alias(*, name, repo, target): | ||
| return """\ | ||
| package(default_visibility = ["//visibility:public"]) | ||
|
|
||
| alias( | ||
| name = "pkg", | ||
| actual = "@{repo}//:{target}", | ||
| ) | ||
|
|
||
| alias( | ||
| name = "whl", | ||
| actual = "@{repo}//:{target}", | ||
| ) | ||
| """.format( | ||
| repo = repo, | ||
| target = target, | ||
| ) |
There was a problem hiding this comment.
The _render_extra_alias function generates a static alias pointing to a single spoke repository. However, in a multi-version or multi-platform setup, there are multiple spoke repositories. We should replace this with a multiplatform alias generator that uses select to choose the correct spoke repository based on the active configuration.
def _render_extra_alias_multiplatform(*, name, repo_mapping, target_suffix):
if type(repo_mapping) == type(""):
actual_expr = repr("@{}//:{}".format(repo_mapping, target_suffix))
load_statement = ""
else:
actual_dict = {}
for config_setting, repo_name in repo_mapping.items():
key = _repr_config_setting(config_setting)
actual_dict[key] = repr("@{}//:{}".format(repo_name, target_suffix))
if len(actual_dict) == 1 and list(actual_dict.keys())[0] == repr("//conditions:default"):
actual_expr = list(actual_dict.values())[0]
else:
sorted_pairs = sorted(actual_dict.items())
actual_expr = "select({\n"
for k, v in sorted_pairs:
actual_expr += " {}: {},\n".format(k, v)
actual_expr += " })"
needs_load = any(["whl_config_setting" in k for k in actual_dict.keys()])
load_statement = ""
if needs_load:
load_statement = 'load("@rules_python//python/private/pypi:whl_config_setting.bzl", "whl_config_setting")\n'
return """\
{load_statement}package(default_visibility = ["//visibility:public"])
alias(
name = "pkg",
actual = {actual_expr},
)
alias(
name = "whl",
actual = {actual_expr},
)
""".format(
load_statement = load_statement,
actual_expr = actual_expr,
)
| # Generate extra alias directories for optimized mode extras. | ||
| for name, extras_info in whl_extras.items(): | ||
| normalized = normalize_name(name) | ||
| for repo_name, extra_names in extras_info.items(): | ||
| for extra in extra_names: | ||
| extra_pkg = "{}_{}".format(normalized, extra) | ||
| files["{}/BUILD.bazel".format(extra_pkg)] = _render_extra_alias( | ||
| name = extra_pkg, | ||
| repo = repo_name, | ||
| target = "{}__{}".format(normalized, extra), | ||
| ) | ||
|
|
||
| # Also create pkg__ alias (no extras) | ||
| no_extras_pkg = "{}__".format(normalized) | ||
| files["{}/BUILD.bazel".format(no_extras_pkg)] = _render_extra_alias( | ||
| name = no_extras_pkg, | ||
| repo = repo_name, | ||
| target = normalized, | ||
| ) |
There was a problem hiding this comment.
In a multi-version or multi-platform setup, whl_extras contains multiple spoke repositories for the same wheel. Iterating over extras_info.items() and writing directly to files causes the alias for one spoke to overwrite the others, breaking multi-version/multi-platform support for extras. We should use the multiplatform alias generator to create a single select-based alias that correctly routes to the appropriate spoke repository.
# Generate extra alias directories for optimized mode extras.
for name, extras_info in whl_extras.items():
normalized = normalize_name(name)
pkg_aliases = aliases.get(normalized)
if not pkg_aliases:
continue
# Find all unique extras across all repos for this wheel
unique_extras = {}
for extra_names in extras_info.values():
for extra in extra_names:
unique_extras[extra] = True
for extra in unique_extras:
extra_pkg = "{}_{}".format(normalized, extra)
files["{}/BUILD.bazel".format(extra_pkg)] = _render_extra_alias_multiplatform(
name = extra_pkg,
repo_mapping = pkg_aliases,
target_suffix = "{}__{}".format(normalized, extra),
)
# Also create pkg__ alias (no extras)
no_extras_pkg = "{}__".format(normalized)
files["{}/BUILD.bazel".format(no_extras_pkg)] = _render_extra_alias_multiplatform(
name = no_extras_pkg,
repo_mapping = pkg_aliases,
target_suffix = normalized,
)
| norm_pkg = normalize_name(whl_name) | ||
| for extra_names in extras_info.values(): | ||
| for extra in extra_names: | ||
| alias_name = "%s__%s" % (norm_pkg, extra) |
There was a problem hiding this comment.
There is a naming mismatch between the unified hub alias name and the hub repository package name. The unified hub alias is generated with double underscores __ (e.g., requests__security), whereas render_pkg_aliases.bzl generates the hub repository package with a single underscore _ (e.g., requests_security). This mismatch will cause the unified hub aliases to point to non-existent packages. Update this to use a single underscore to match the hub repository package naming.
| alias_name = "%s__%s" % (norm_pkg, extra) | |
| alias_name = "%s_%s" % (norm_pkg, extra) |
| if not extra_only_deps and not extra_only_deps_select: | ||
| continue |
There was a problem hiding this comment.
Skipping the generation of the pkg__extra target when there are no extra dependencies (or when all extra dependencies are already present in the base dependencies) will cause the hub repository's alias to point to a non-existent target, leading to build failures. We should always generate the pkg__extra target, even if it only depends on ":pkg", to ensure that the hub aliases remain valid.
rickeylev
left a comment
There was a problem hiding this comment.
saw this was draft, so didn't look too thoroughly, just for things that looked like the bot was obviously wrong
|
Again, ran out of free tokens, so will postpone for another day. |
453b0b3 to
23c95ab
Compare
This is no longer used starting when we enabled pipstar by default and did a code cleanup where Python is no longer used to extract the wheels. Split from bazel-contrib#3856
|
23c95ab to
e433644
Compare
|
So it seems that it is much better to just separate the repositories - one with extracted whl sources, one with the parsed METADATA. This way the change is surgical and very easy to reason about. This was spiked by hand and then vibed and debugged. The build should work but there are many things missing. TODO:
|
db1494b to
c3a21b3
Compare
|
/review |
|
This should now work. EDIT: just realized that merging |
|
/review |
…ive and pip_archive (bazel-contrib#3948) Before this PR the `whl_library` would be a do-all repository rule. Whilst it is convenient to reuse the code, it is actually really difficult to maintain and make it more performant. Side effect here is that the python dependencies (like `setuptools`, etc) will no longer be downloaded for whl-only extracts, it makes it a tiny bit faster. With this split we can drop certain dependencies from the whl extraction and optimize the common path - whl extraction where the URL for downloading the wheel is known. This also allows us to start handling the sdists in an entirely different way. In a followup PR I plan to split the part which just extracts the wheel to lay a more surgical foundation to bazel-contrib#3856. Foundation work for bazel-contrib#2410. Split out of bazel-contrib#3856. Work towards bazel-contrib#2948. --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com>
With this we are starting to separate some of the targets. Split out of bazel-contrib#3856 Work towards bazel-contrib#2948
) With this we are starting to separate whl_library_targets into 2 parts - one for sources only (without deps) and another one is just the deps parts. Next PR I'll create a way to create 2 separate instances. Split out of bazel-contrib#3856 Work towards bazel-contrib#2948
Summary: - Add a new repo rule to just read metadata.json - Add integration tests for the repository rules in `whl_library.bzl` file. - Make some of the arguments optional in the BUILD.bazel code generation. No changelog, because the rule is not yet exposed to the user in any way. Split out of bazel-contrib#3856 Work towards bazel-contrib#2948 Fixes bazel-contrib#3071 --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com>
80a0a90 to
ffbe7c1
Compare
|
OK, I personally like the fact that everything is done as part of |
|
I'll add some unit tests for piece of mind. |
…util_whls Update site-packages repository path prefixes to match the shared wheel extraction repos introduced by PR bazel-contrib#3856.
|
Hmmmm. This is an interesting one: I think in this particular case, we could just add the
|
|
Extra things that we should do:
|
This is the common denominator and for now no warnings are printed, but there are opportunities to do this. This approach is way more surgical than the previous one. Fixes bazel-contrib#2948
1441ff7 to
8a10382
Compare
|
Given the recent refactors, I've did it again in a more surgical way. |
Summary:
like Windows, where path length is sometimes an issue.
Limitations:
reuse across all of the hubs, but then we need to solve the "how to tell users
that we need to have a single index per package? how to customize it?" problem.
then we stop reuse for that particular wheel file. This should be rare?
Extra thoughts on the design:
the dep graph can be passed to the hub repo.
Once #3791 is resolved this should bring reasonable speedups, especially if there the same wheel used in multiple places.
Fixes #2948
Work towards #2530