Skip to content

Commit fc2a9f9

Browse files
aignasrickeylevgemini-code-assist[bot]
authored
fix(pipstar): fix whl extraction and flip pipstar=true (#3461)
Attempt number 2. This should be smoother this time and should not cause any breakage because we are not enabling any cross-building by default and only the host wheels will be present. Because we also started extracting using starlark APIs, some extra fixups where needed because some wheels require extracting `.data` files into correct paths. This also adds the `INSTALLER` file after extracting files to signify that `pipstar` has installed the file. Because we have stopped passing hermetic interpreter to the `whl_library` if pipstar is enabled, we also needed to ensure that the code path is only enabled if the extraction with pipstar is supported (i.e. bazel >= 8). Fixes #2949 --------- Co-authored-by: Richard Levasseur <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 50fb48e commit fc2a9f9

File tree

7 files changed

+117
-45
lines changed

7 files changed

+117
-45
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ END_UNRELEASED_TEMPLATE
8181
* (pip) `pipstar` has been enabled for all `whl_library` instances where the whl
8282
is passed through a label or downloaded using the bazel downloader
8383
([#2949](https://github.com/bazel-contrib/rules_python/issues/2949)).
84+
* (pypi) `pipstar` flag default has been flipped to be on by default.
85+
It can be disabled through `RULES_PYTHON_ENABLE_PIPSTAR=0` environment variable.
86+
If you do need to disable it, please add a comment to
87+
[#2949](https://github.com/bazel-contrib/rules_python/issues/2949).
8488
* (gazelle deps) rules_go bumped from 0.55.1 to 0.59.0
8589
* (gazelle deps) gazelle bumped from 0.36.0 to 0.47.0
8690

python/private/internal_config_repo.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ load("//python/private:text_util.bzl", "render")
2222
load(":repo_utils.bzl", "repo_utils")
2323

2424
_ENABLE_PIPSTAR_ENVVAR_NAME = "RULES_PYTHON_ENABLE_PIPSTAR"
25-
_ENABLE_PIPSTAR_DEFAULT = "0"
25+
_ENABLE_PIPSTAR_DEFAULT = "1"
2626
_ENABLE_DEPRECATION_WARNINGS_ENVVAR_NAME = "RULES_PYTHON_DEPRECATION_WARNINGS"
2727
_ENABLE_DEPRECATION_WARNINGS_DEFAULT = "0"
2828

python/private/pypi/extension.bzl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ def _configure(config, *, override = False, **kwargs):
7070
def build_config(
7171
*,
7272
module_ctx,
73-
enable_pipstar):
73+
enable_pipstar,
74+
enable_pipstar_extract):
7475
"""Parse 'configure' and 'default' extension tags
7576
7677
Args:
7778
module_ctx: {type}`module_ctx` module context.
7879
enable_pipstar: {type}`bool` a flag to enable dropping Python dependency for
7980
evaluation of the extension.
81+
enable_pipstar_extract: {type}`bool | None` a flag to also not pass Python
82+
interpreter to `whl_library` when possible.
8083
8184
Returns:
8285
A struct with the configuration.
@@ -127,13 +130,15 @@ def build_config(
127130
for name, values in defaults["platforms"].items()
128131
},
129132
enable_pipstar = enable_pipstar,
133+
enable_pipstar_extract = enable_pipstar_extract,
130134
)
131135

132136
def parse_modules(
133137
module_ctx,
134138
_fail = fail,
135139
simpleapi_download = simpleapi_download,
136140
enable_pipstar = False,
141+
enable_pipstar_extract = False,
137142
**kwargs):
138143
"""Implementation of parsing the tag classes for the extension and return a struct for registering repositories.
139144
@@ -142,6 +147,8 @@ def parse_modules(
142147
simpleapi_download: Used for testing overrides
143148
enable_pipstar: {type}`bool` a flag to enable dropping Python dependency for
144149
evaluation of the extension.
150+
enable_pipstar_extract: {type}`bool` a flag to enable dropping Python dependency for
151+
extracting wheels.
145152
_fail: {type}`function` the failure function, mainly for testing.
146153
**kwargs: Extra arguments passed to the hub_builder.
147154
@@ -179,7 +186,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
179186
srcs_exclude_glob = whl_mod.srcs_exclude_glob,
180187
)
181188

182-
config = build_config(module_ctx = module_ctx, enable_pipstar = enable_pipstar)
189+
config = build_config(module_ctx = module_ctx, enable_pipstar = enable_pipstar, enable_pipstar_extract = enable_pipstar_extract)
183190

184191
# TODO @aignas 2025-06-03: Merge override API with the builder?
185192
_overriden_whl_set = {}
@@ -362,7 +369,7 @@ def _pip_impl(module_ctx):
362369
module_ctx: module contents
363370
"""
364371

365-
mods = parse_modules(module_ctx, enable_pipstar = rp_config.enable_pipstar)
372+
mods = parse_modules(module_ctx, enable_pipstar = rp_config.enable_pipstar, enable_pipstar_extract = rp_config.enable_pipstar and rp_config.bazel_8_or_later)
366373

367374
# Build all of the wheel modifications if the tag class is called.
368375
_whl_mods_impl(mods.whl_mods)

python/private/pypi/hub_builder.bzl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def _pip_parse(self, module_ctx, pip_attr):
153153
module_ctx,
154154
pip_attr = pip_attr,
155155
enable_pipstar = self._config.enable_pipstar or self._get_index_urls.get(pip_attr.python_version),
156+
enable_pipstar_extract = self._config.enable_pipstar_extract or self._get_index_urls.get(pip_attr.python_version),
156157
)
157158

158159
### end of PUBLIC methods
@@ -407,14 +408,16 @@ def _create_whl_repos(
407408
module_ctx,
408409
*,
409410
pip_attr,
410-
enable_pipstar = False):
411+
enable_pipstar = False,
412+
enable_pipstar_extract = False):
411413
"""create all of the whl repositories
412414
413415
Args:
414416
self: the builder.
415417
module_ctx: {type}`module_ctx`.
416418
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
417419
enable_pipstar: {type}`bool` - enable the pipstar or not.
420+
enable_pipstar_extract: {type}`bool` - enable the pipstar extraction or not.
418421
"""
419422
logger = self._logger
420423
platforms = self._platforms[pip_attr.python_version]
@@ -479,6 +482,7 @@ def _create_whl_repos(
479482
is_multiple_versions = whl.is_multiple_versions,
480483
interpreter = interpreter,
481484
enable_pipstar = enable_pipstar,
485+
enable_pipstar_extract = enable_pipstar_extract,
482486
)
483487
_add_whl_library(
484488
self,
@@ -555,7 +559,8 @@ def _whl_repo(
555559
python_version,
556560
use_downloader,
557561
interpreter,
558-
enable_pipstar = False):
562+
enable_pipstar = False,
563+
enable_pipstar_extract = False):
559564
args = dict(whl_library_args)
560565
args["requirement"] = src.requirement_line
561566
is_whl = src.filename.endswith(".whl")
@@ -567,7 +572,7 @@ def _whl_repo(
567572
# need to pass the extra args there, so only pop this for whls
568573
args["extra_pip_args"] = src.extra_pip_args
569574

570-
if "whl_patches" in args or not (enable_pipstar and is_whl):
575+
if "whl_patches" in args or not (enable_pipstar_extract and is_whl):
571576
if interpreter.path:
572577
args["python_interpreter"] = interpreter.path
573578
if interpreter.target:

python/private/pypi/whl_library.bzl

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ load(":parse_whl_name.bzl", "parse_whl_name")
2626
load(":patch_whl.bzl", "patch_whl")
2727
load(":pep508_requirement.bzl", "requirement")
2828
load(":pypi_repo_utils.bzl", "pypi_repo_utils")
29-
load(":whl_metadata.bzl", "whl_metadata")
29+
load(":whl_metadata.bzl", "find_whl_metadata", "whl_metadata")
3030
load(":whl_target_platforms.bzl", "whl_target_platforms")
3131

3232
_CPPFLAGS = "CPPFLAGS"
@@ -265,6 +265,79 @@ def _create_repository_execution_environment(rctx, python_interpreter, logger =
265265
env[_CPPFLAGS] = " ".join(cppflags)
266266
return env
267267

268+
def _extract_whl_star(rctx, *, whl_path, logger):
269+
install_dir_path = whl_path.dirname.get_child("site-packages")
270+
repo_utils.extract(
271+
rctx,
272+
archive = whl_path,
273+
output = install_dir_path,
274+
supports_whl_extraction = rp_config.supports_whl_extraction,
275+
)
276+
metadata_file = find_whl_metadata(
277+
install_dir = install_dir_path,
278+
logger = logger,
279+
)
280+
281+
# Get the <prefix>.dist_info dir name
282+
dist_info_dir = metadata_file.dirname
283+
rctx.file(
284+
dist_info_dir.get_child("INSTALLER"),
285+
"https://github.com/bazel-contrib/rules_python#pipstar",
286+
)
287+
repo_root_dir = whl_path.dirname
288+
289+
# Get the <prefix>.dist_info dir name
290+
data_dir = dist_info_dir.dirname.get_child(dist_info_dir.basename[:-len(".dist-info")] + ".data")
291+
if data_dir.exists:
292+
for prefix, dest in {
293+
# https://docs.python.org/3/library/sysconfig.html#posix-prefix
294+
# We are taking this from the legacy whl installer config
295+
"data": "data",
296+
"headers": "include",
297+
"platlib": "site-packages",
298+
"purelib": "site-packages",
299+
"scripts": "bin",
300+
}.items():
301+
src = data_dir.get_child(prefix)
302+
dest = repo_root_dir.get_child(dest)
303+
if src.exists:
304+
rctx.rename(src, dest)
305+
306+
# TODO @aignas 2025-12-16: when moving scripts to `bin`, rewrite the #!python
307+
# shebang to be something else, for inspiration look at the hermetic
308+
# toolchain wrappers
309+
310+
def _extract_whl_py(rctx, *, python_interpreter, args, whl_path, environment, logger):
311+
target_platforms = rctx.attr.experimental_target_platforms or []
312+
if target_platforms:
313+
parsed_whl = parse_whl_name(whl_path.basename)
314+
315+
# NOTE @aignas 2023-12-04: if the wheel is a platform specific wheel, we
316+
# only include deps for that target platform
317+
if parsed_whl.platform_tag != "any":
318+
target_platforms = [
319+
p.target_platform
320+
for p in whl_target_platforms(
321+
platform_tag = parsed_whl.platform_tag,
322+
abi_tag = parsed_whl.abi_tag.strip("tm"),
323+
)
324+
]
325+
326+
pypi_repo_utils.execute_checked(
327+
rctx,
328+
op = "whl_library.ExtractWheel({}, {})".format(rctx.attr.name, whl_path),
329+
python = python_interpreter,
330+
arguments = args + [
331+
"--whl-file",
332+
whl_path,
333+
] + ["--platform={}".format(p) for p in target_platforms],
334+
srcs = rctx.attr._python_srcs,
335+
environment = environment,
336+
quiet = rctx.attr.quiet,
337+
timeout = rctx.attr.timeout,
338+
logger = logger,
339+
)
340+
268341
def _whl_library_impl(rctx):
269342
logger = repo_utils.logger(rctx)
270343
python_interpreter = pypi_repo_utils.resolve_python_interpreter(
@@ -327,6 +400,8 @@ def _whl_library_impl(rctx):
327400

328401
# also enable pipstar for any whls that are downloaded without `pip`
329402
enable_pipstar = (rp_config.enable_pipstar or whl_path) and rctx.attr.config_load
403+
enable_pipstar_extract = (rp_config.enable_pipstar and rp_config.bazel_8_or_later) and rctx.attr.config_load
404+
330405
if not whl_path:
331406
if rctx.attr.urls:
332407
op_tmpl = "whl_library.BuildWheelFromSource({name}, {requirement})"
@@ -372,19 +447,24 @@ def _whl_library_impl(rctx):
372447
timeout = rctx.attr.timeout,
373448
)
374449

450+
if enable_pipstar_extract:
451+
_extract_whl_star(rctx, whl_path = whl_path, logger = logger)
452+
else:
453+
_extract_whl_py(
454+
rctx,
455+
python_interpreter = python_interpreter,
456+
args = args,
457+
whl_path = whl_path,
458+
environment = environment,
459+
logger = logger,
460+
)
461+
375462
# NOTE @aignas 2025-09-28: if someone has an old vendored file that does not have the
376463
# dep_template set or the packages is not set either, we should still not break, best to
377464
# disable pipstar for that particular case.
378465
#
379466
# Remove non-pipstar and config_load check when we release rules_python 2.
380467
if enable_pipstar:
381-
repo_utils.extract(
382-
rctx,
383-
archive = whl_path,
384-
output = "site-packages",
385-
supports_whl_extraction = rp_config.supports_whl_extraction,
386-
)
387-
388468
install_dir_path = whl_path.dirname.get_child("site-packages")
389469
metadata = whl_metadata(
390470
install_dir = install_dir_path,
@@ -439,36 +519,6 @@ def _whl_library_impl(rctx):
439519
extras = requirement(rctx.attr.requirement).extras,
440520
)
441521
else:
442-
target_platforms = rctx.attr.experimental_target_platforms or []
443-
if target_platforms:
444-
parsed_whl = parse_whl_name(whl_path.basename)
445-
446-
# NOTE @aignas 2023-12-04: if the wheel is a platform specific wheel, we
447-
# only include deps for that target platform
448-
if parsed_whl.platform_tag != "any":
449-
target_platforms = [
450-
p.target_platform
451-
for p in whl_target_platforms(
452-
platform_tag = parsed_whl.platform_tag,
453-
abi_tag = parsed_whl.abi_tag.strip("tm"),
454-
)
455-
]
456-
457-
pypi_repo_utils.execute_checked(
458-
rctx,
459-
op = "whl_library.ExtractWheel({}, {})".format(rctx.attr.name, whl_path),
460-
python = python_interpreter,
461-
arguments = args + [
462-
"--whl-file",
463-
whl_path,
464-
] + ["--platform={}".format(p) for p in target_platforms],
465-
srcs = rctx.attr._python_srcs,
466-
environment = environment,
467-
quiet = rctx.attr.quiet,
468-
timeout = rctx.attr.timeout,
469-
logger = logger,
470-
)
471-
472522
metadata = json.decode(rctx.read("metadata.json"))
473523
rctx.delete("metadata.json")
474524

tests/pypi/extension/extension_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def _build_config(env, enable_pipstar = 0, **kwargs):
9999
return env.expect.that_struct(
100100
build_config(
101101
enable_pipstar = enable_pipstar,
102+
enable_pipstar_extract = True,
102103
**kwargs
103104
),
104105
attrs = dict(

tests/pypi/hub_builder/hub_builder_tests.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ simple==0.0.1 \
4141
def hub_builder(
4242
env,
4343
enable_pipstar = True,
44+
enable_pipstar_extract = True,
4445
debug = False,
4546
config = None,
4647
minor_mapping = {},
@@ -54,6 +55,7 @@ def hub_builder(
5455
config = config or struct(
5556
# no need to evaluate the markers with the interpreter
5657
enable_pipstar = enable_pipstar,
58+
enable_pipstar_extract = enable_pipstar_extract,
5759
platforms = {
5860
"{}_{}{}".format(os, cpu, freethreaded): _plat(
5961
name = "{}_{}{}".format(os, cpu, freethreaded),
@@ -512,6 +514,7 @@ def _test_torch_experimental_index_url(env):
512514
config = struct(
513515
netrc = None,
514516
enable_pipstar = True,
517+
enable_pipstar_extract = True,
515518
auth_patterns = {},
516519
platforms = {
517520
"{}_{}".format(os, cpu): _plat(
@@ -1095,6 +1098,7 @@ def _test_pipstar_platforms(env):
10951098
enable_pipstar = True,
10961099
config = struct(
10971100
enable_pipstar = True,
1101+
enable_pipstar_extract = True,
10981102
netrc = None,
10991103
auth_patterns = {},
11001104
platforms = {
@@ -1179,6 +1183,7 @@ def _test_pipstar_platforms_limit(env):
11791183
enable_pipstar = True,
11801184
config = struct(
11811185
enable_pipstar = True,
1186+
enable_pipstar_extract = True,
11821187
netrc = None,
11831188
auth_patterns = {},
11841189
platforms = {

0 commit comments

Comments
 (0)