Skip to content

Commit 4d9c2b1

Browse files
aignasrickeylev
andauthored
feat(pip.parse): limit the target platforms we parse requirements for (#3441)
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 Work towards #3434 --------- Co-authored-by: Richard Levasseur <richardlev@gmail.com>
1 parent 759f5da commit 4d9c2b1

File tree

7 files changed

+148
-9
lines changed

7 files changed

+148
-9
lines changed

CHANGELOG.md

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

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

python/private/pypi/extension.bzl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,26 @@ EXPERIMENTAL: this may be removed without notice.
667667
668668
:::{versionadded} 1.4.0
669669
:::
670+
""",
671+
),
672+
"target_platforms": attr.string_list(
673+
default = ["{os}_{arch}"],
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+
:::{include} /_includes/experimental_api.md
686+
:::
687+
688+
:::{versionadded} VERSION_NEXT_FEATURE
689+
:::
670690
""",
671691
),
672692
"whl_modifications": attr.label_keyed_string_dict(

python/private/pypi/hub_builder.bzl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
load("//python/private:full_version.bzl", "full_version")
44
load("//python/private:normalize_name.bzl", "normalize_name")
5+
load("//python/private:repo_utils.bzl", "repo_utils")
56
load("//python/private:version.bzl", "version")
67
load("//python/private:version_label.bzl", "version_label")
78
load(":attrs.bzl", "use_isolated")
@@ -135,11 +136,15 @@ def _pip_parse(self, module_ctx, pip_attr):
135136
))
136137
return
137138

139+
default_cross_setup = _set_get_index_urls(self, pip_attr)
138140
self._platforms[python_version] = _platforms(
141+
module_ctx,
139142
python_version = full_python_version,
140143
config = self._config,
144+
# FIXME @aignas 2025-12-06: should we have this behaviour?
145+
# TODO @aignas 2025-12-06: use target_platforms always even when the get_index_urls is set.
146+
target_platforms = [] if default_cross_setup else pip_attr.target_platforms,
141147
)
142-
_set_get_index_urls(self, pip_attr)
143148
_add_group_map(self, pip_attr.experimental_requirement_cycles)
144149
_add_extra_aliases(self, pip_attr.extra_hub_aliases)
145150
_create_whl_repos(
@@ -249,7 +254,7 @@ def _set_get_index_urls(self, pip_attr):
249254

250255
# parallel_download is set to True by default, so we are not checking/validating it
251256
# here
252-
return
257+
return False
253258

254259
python_version = pip_attr.python_version
255260
self._use_downloader.setdefault(python_version, {}).update({
@@ -275,6 +280,7 @@ def _set_get_index_urls(self, pip_attr):
275280
cache = self._simpleapi_cache,
276281
parallel_download = pip_attr.parallel_download,
277282
)
283+
return True
278284

279285
def _detect_interpreter(self, pip_attr):
280286
python_interpreter_target = pip_attr.python_interpreter_target
@@ -301,14 +307,25 @@ def _detect_interpreter(self, pip_attr):
301307
path = pip_attr.python_interpreter,
302308
)
303309

304-
def _platforms(*, python_version, config):
310+
def _platforms(module_ctx, *, python_version, config, target_platforms):
305311
platforms = {}
306312
python_version = version.parse(
307313
python_version,
308314
strict = True,
309315
)
310316

317+
target_platforms = sorted({
318+
p.format(
319+
os = repo_utils.get_platforms_os_name(module_ctx),
320+
arch = repo_utils.get_platforms_cpu_name(module_ctx),
321+
): None
322+
for p in target_platforms
323+
})
324+
311325
for platform, values in config.platforms.items():
326+
if target_platforms and platform not in target_platforms:
327+
continue
328+
312329
# TODO @aignas 2025-07-07: this is probably doing the parsing of the version too
313330
# many times.
314331
abi = "{}{}{}.{}".format(

python/private/pypi/requirements_files_by_platform.bzl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ def requirements_files_by_platform(
140140

141141
platforms_from_args = _platforms_from_args(extra_pip_args)
142142
if logger:
143-
logger.debug(lambda: "Platforms from pip args: {}".format(platforms_from_args))
143+
logger.debug(lambda: "Platforms from pip args: {} (from {})".format(platforms_from_args, extra_pip_args))
144144

145-
default_platforms = platforms
145+
input_platforms = platforms
146+
default_platforms = [_platform(p, python_version) for p in platforms]
146147

147148
if platforms_from_args:
148149
lock_files = [
@@ -174,6 +175,7 @@ def requirements_files_by_platform(
174175
platform
175176
for filter_or_platform in specifier.split(",")
176177
for platform in (_default_platforms(filter = filter_or_platform, platforms = platforms) if filter_or_platform.endswith("*") else [filter_or_platform])
178+
if _platform(platform, python_version) in default_platforms
177179
]
178180
for file, specifier in requirements_by_platform.items()
179181
}.items()
@@ -227,9 +229,10 @@ def requirements_files_by_platform(
227229
configured_platforms[p] = file
228230

229231
elif logger:
230-
logger.warn(lambda: "File {} will be ignored because there are no configured platforms: {}".format(
232+
logger.info(lambda: "File {} will be ignored because there are no configured platforms: {} out of {}".format(
231233
file,
232234
default_platforms,
235+
input_platforms,
233236
))
234237
continue
235238

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: 74 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 \
@@ -723,6 +723,10 @@ simple==0.0.3 \
723723
"requirements.linux_x86_64.txt": "linux_x86_64",
724724
"requirements.osx_aarch64.txt": "osx_aarch64",
725725
},
726+
target_platforms = [
727+
"linux_x86_64",
728+
"osx_aarch64",
729+
],
726730
),
727731
)
728732
pypi = builder.build()
@@ -1221,6 +1225,73 @@ optimum[onnxruntime-gpu]==1.17.1 ; sys_platform == 'linux'
12211225

12221226
_tests.append(_test_pipstar_platforms)
12231227

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

tests/pypi/requirements_files_by_platform/requirements_files_by_platform_tests.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ def _test_simple_limited(env):
115115
},
116116
platforms = ["linux_x86_64", "osx_x86_64"],
117117
),
118+
requirements_files_by_platform(
119+
requirements_by_platform = {
120+
"requirements_lock": "linux_x86_64,osx_aarch64,osx_x86_64",
121+
},
122+
platforms = ["linux_x86_64", "osx_x86_64", "windows_x86_64"],
123+
),
118124
]:
119125
env.expect.that_dict(got).contains_exactly({
120126
"requirements_lock": [
@@ -219,6 +225,17 @@ def _test_os_arch_requirements_with_default(env):
219225
"requirements_linux": "linux_x86_64,linux_aarch64",
220226
},
221227
requirements_lock = "requirements_lock",
228+
platforms = [
229+
"linux_super_exotic",
230+
"linux_x86_64",
231+
"linux_aarch64",
232+
"linux_arm",
233+
"linux_ppc",
234+
"linux_s390x",
235+
"osx_aarch64",
236+
"osx_x86_64",
237+
"windows_x86_64",
238+
],
222239
)
223240
env.expect.that_dict(got).contains_exactly({
224241
"requirements_exotic": ["linux_super_exotic"],

0 commit comments

Comments
 (0)