Skip to content

Commit 6b7d394

Browse files
committed
feat(pip.parse): limit the target platforms we parse requirements for
Up until now the users can configure which requirements files to be used for specific platforms, however, what they cannot configure is what target platforms should actually be set up. The difference in the problems is: 1. I want my `bazel build` to work on `osx aarch64` and `linux x86_64`. 1. I want my `bazel build` to build for `linux x86_64` on `osx aarch64`. With the newly introduced `target_platforms` attribute users can finally specify their target platforms. To ensure that this also allows users to specify that they want to support `freethreaded` and `non-freethreaded` platforms at the same time we support `{os}` and `{arch}` templating in the strings. This should fix the `genquery` usage pattern breakage when we previously enabled `RULES_PYTHON_ENABLE_PIPSTAR=1`. Work towards #2949
1 parent 411b937 commit 6b7d394

6 files changed

Lines changed: 134 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ END_UNRELEASED_TEMPLATE
100100
{#v0-0-0-added}
101101
### Added
102102
* (toolchains) `3.9.25` Python toolchain from [20251031] release.
103+
* (pypi) API to tell `pip.parse` which platforms users care about. This is very useful to ensure
104+
that when users do `bazel query` for their deps, they don't have to download all of the
105+
dependencies for all of the available wheels. Torch wheels can be up of 1GB and it takes a lot
106+
of time to download those, which is unnecessary if only the host platform builds are necessary
107+
to be performed. This is mainly for backwards/forwards compatibility whilst rolling out
108+
`RULES_PYTHON_ENABLE_PIPSTAR=1` by default. Users of `experimental_index_url` that perform
109+
cross-builds should add {obj}`target_platforms` to their `pip.parse` invocations, which will
110+
become mandatory if any cross-builds are required from the next release.
103111

104112
[20251031]: https://github.com/astral-sh/python-build-standalone/releases/tag/20251031
105113
{#v1-7-0}

python/private/pypi/extension.bzl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,25 @@ EXPERIMENTAL: this may be removed without notice.
667667
668668
:::{versionadded} 1.4.0
669669
:::
670+
""",
671+
),
672+
"target_platforms": attr.string_list(
673+
default = ["{host}"],
674+
doc = """\
675+
The list of platforms for which we would evaluate the requirements files. If you need to be able to
676+
only evaluate for a particular platform (e.g. "linux_x86_64"), then put it in here.
677+
678+
If you want `freethreaded` variant, then you can use `_freethreaded` suffix as `rules_python` is
679+
defining target platforms for these variants in its `MODULE.bazel` file. The identifiers for this
680+
function in general are the same as used in the {obj}`pip.default.platform` attribute.
681+
682+
If you only care for the host platform and do not have a usecase to cross-build, then you can put in
683+
a string `"{os}_{arch}"` as the value here. You could also use `"{os}_{arch}_freethreaded"` as well.
684+
685+
EXPERIMENTAL: this may be removed without notice.
686+
687+
:::{versionadded} VERSION_NEXT_FEATURE
688+
:::
670689
""",
671690
),
672691
"whl_modifications": attr.label_keyed_string_dict(

python/private/pypi/hub_builder.bzl

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,15 @@ def _pip_parse(self, module_ctx, pip_attr):
135135
))
136136
return
137137

138+
default_cross_setup = _set_get_index_urls(self, pip_attr)
138139
self._platforms[python_version] = _platforms(
140+
module_ctx,
139141
python_version = full_python_version,
140142
config = self._config,
143+
# FIXME @aignas 2025-12-06: should we have this behaviour?
144+
# TODO @aignas 2025-12-06: use target_platforms always even when the get_index_urls is set.
145+
target_platforms = [] if default_cross_setup else pip_attr.target_platforms,
141146
)
142-
_set_get_index_urls(self, pip_attr)
143147
_add_group_map(self, pip_attr.experimental_requirement_cycles)
144148
_add_extra_aliases(self, pip_attr.extra_hub_aliases)
145149
_create_whl_repos(
@@ -249,7 +253,7 @@ def _set_get_index_urls(self, pip_attr):
249253

250254
# parallel_download is set to True by default, so we are not checking/validating it
251255
# here
252-
return
256+
return False
253257

254258
python_version = pip_attr.python_version
255259
self._use_downloader.setdefault(python_version, {}).update({
@@ -275,6 +279,7 @@ def _set_get_index_urls(self, pip_attr):
275279
cache = self._simpleapi_cache,
276280
parallel_download = pip_attr.parallel_download,
277281
)
282+
return True
278283

279284
def _detect_interpreter(self, pip_attr):
280285
python_interpreter_target = pip_attr.python_interpreter_target
@@ -301,14 +306,22 @@ def _detect_interpreter(self, pip_attr):
301306
path = pip_attr.python_interpreter,
302307
)
303308

304-
def _platforms(*, python_version, config):
309+
def _platforms(module_ctx, *, python_version, config, target_platforms):
305310
platforms = {}
306311
python_version = version.parse(
307312
python_version,
308313
strict = True,
309314
)
310315

316+
target_platforms = sorted({
317+
p.format(os = module_ctx.os.name, arch = module_ctx.os.arch): None
318+
for p in target_platforms
319+
})
320+
311321
for platform, values in config.platforms.items():
322+
if target_platforms and platform not in target_platforms:
323+
continue
324+
312325
# TODO @aignas 2025-07-07: this is probably doing the parsing of the version too
313326
# many times.
314327
abi = "{}{}{}.{}".format(
@@ -400,22 +413,24 @@ def _create_whl_repos(
400413
"""
401414
logger = self._logger
402415
platforms = self._platforms[pip_attr.python_version]
416+
requirements_by_platform = requirements_files_by_platform(
417+
requirements_by_platform = pip_attr.requirements_by_platform,
418+
requirements_linux = pip_attr.requirements_linux,
419+
requirements_lock = pip_attr.requirements_lock,
420+
requirements_osx = pip_attr.requirements_darwin,
421+
requirements_windows = pip_attr.requirements_windows,
422+
extra_pip_args = pip_attr.extra_pip_args,
423+
platforms = sorted(platforms), # here we only need keys
424+
python_version = full_version(
425+
version = pip_attr.python_version,
426+
minor_mapping = self._minor_mapping,
427+
),
428+
logger = logger,
429+
)
430+
403431
requirements_by_platform = parse_requirements(
404432
module_ctx,
405-
requirements_by_platform = requirements_files_by_platform(
406-
requirements_by_platform = pip_attr.requirements_by_platform,
407-
requirements_linux = pip_attr.requirements_linux,
408-
requirements_lock = pip_attr.requirements_lock,
409-
requirements_osx = pip_attr.requirements_darwin,
410-
requirements_windows = pip_attr.requirements_windows,
411-
extra_pip_args = pip_attr.extra_pip_args,
412-
platforms = sorted(platforms), # here we only need keys
413-
python_version = full_version(
414-
version = pip_attr.python_version,
415-
minor_mapping = self._minor_mapping,
416-
),
417-
logger = logger,
418-
),
433+
requirements_by_platform = requirements_by_platform,
419434
platforms = platforms,
420435
extra_pip_args = pip_attr.extra_pip_args,
421436
get_index_urls = self._get_index_urls.get(pip_attr.python_version),

python/private/pypi/requirements_files_by_platform.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def requirements_files_by_platform(
227227
configured_platforms[p] = file
228228

229229
elif logger:
230-
logger.warn(lambda: "File {} will be ignored because there are no configured platforms: {}".format(
230+
logger.debug(lambda: "File {} will be ignored because there are no configured platforms: {}".format(
231231
file,
232232
default_platforms,
233233
))

tests/pypi/extension/pip_parse.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def pip_parse(
2727
requirements_linux = None,
2828
requirements_lock = None,
2929
requirements_windows = None,
30+
target_platforms = [],
3031
simpleapi_skip = [],
3132
timeout = 600,
3233
whl_modifications = {},
@@ -41,7 +42,9 @@ def pip_parse(
4142
envsubst = envsubst,
4243
experimental_index_url = experimental_index_url,
4344
experimental_requirement_cycles = experimental_requirement_cycles,
45+
# TODO @aignas 2025-12-02: decide on a single attr - should we reuse this?
4446
experimental_target_platforms = experimental_target_platforms,
47+
target_platforms = target_platforms,
4548
extra_hub_aliases = extra_hub_aliases,
4649
extra_pip_args = extra_pip_args,
4750
hub_name = hub_name,

tests/pypi/hub_builder/hub_builder_tests.bzl

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ load("//tests/pypi/extension:pip_parse.bzl", _parse = "pip_parse")
2525

2626
_tests = []
2727

28-
def _mock_mctx(environ = {}, read = None):
28+
def _mock_mctx(os = "unittest", arch = "exotic", environ = {}, read = None):
2929
return struct(
3030
os = struct(
3131
environ = environ,
32-
name = "unittest",
33-
arch = "exotic",
32+
name = os,
33+
arch = arch,
3434
),
3535
read = read or (lambda _: """\
3636
simple==0.0.1 \
@@ -1221,6 +1221,74 @@ optimum[onnxruntime-gpu]==1.17.1 ; sys_platform == 'linux'
12211221

12221222
_tests.append(_test_pipstar_platforms)
12231223

1224+
def _test_pipstar_platforms_limit(env):
1225+
builder = hub_builder(
1226+
env,
1227+
enable_pipstar = True,
1228+
debug = True,
1229+
config = struct(
1230+
enable_pipstar = True,
1231+
netrc = None,
1232+
auth_patterns = {},
1233+
platforms = {
1234+
"my{}{}".format(os, cpu): _plat(
1235+
name = "my{}{}".format(os, cpu),
1236+
os_name = os,
1237+
arch_name = cpu,
1238+
marker = "python_version ~= \"3.13\"",
1239+
config_settings = [
1240+
"@platforms//os:{}".format(os),
1241+
"@platforms//cpu:{}".format(cpu),
1242+
],
1243+
)
1244+
for os, cpu in [
1245+
("linux", "x86_64"),
1246+
("osx", "aarch64"),
1247+
]
1248+
},
1249+
),
1250+
)
1251+
builder.pip_parse(
1252+
_mock_mctx(
1253+
os = "linux",
1254+
arch = "x86_64",
1255+
read = lambda x: {
1256+
"universal.txt": """\
1257+
optimum[onnxruntime]==1.17.1 ; sys_platform == 'darwin'
1258+
optimum[onnxruntime-gpu]==1.17.1 ; sys_platform == 'linux'
1259+
""",
1260+
}[x],
1261+
),
1262+
_parse(
1263+
hub_name = "pypi",
1264+
python_version = "3.15",
1265+
requirements_lock = "universal.txt",
1266+
target_platforms = ["my{os}{arch}"],
1267+
),
1268+
)
1269+
pypi = builder.build()
1270+
1271+
pypi.exposed_packages().contains_exactly(["optimum"])
1272+
pypi.group_map().contains_exactly({})
1273+
pypi.whl_map().contains_exactly({
1274+
"optimum": {
1275+
"pypi_315_optimum": [
1276+
whl_config_setting(version = "3.15"),
1277+
],
1278+
},
1279+
})
1280+
pypi.whl_libraries().contains_exactly({
1281+
"pypi_315_optimum": {
1282+
"config_load": "@pypi//:config.bzl",
1283+
"dep_template": "@pypi//{name}:{target}",
1284+
"python_interpreter_target": "unit_test_interpreter_target",
1285+
"requirement": "optimum[onnxruntime-gpu]==1.17.1",
1286+
},
1287+
})
1288+
pypi.extra_aliases().contains_exactly({})
1289+
1290+
_tests.append(_test_pipstar_platforms_limit)
1291+
12241292
def hub_builder_test_suite(name):
12251293
"""Create the test suite.
12261294

0 commit comments

Comments
 (0)