Skip to content

Commit fd89eca

Browse files
committed
fix: use the python micro version to parse whl metadata in bzlmod
Add `<micro>` version to the target platform. Instead of `cpxy_os_cpu` the target platform string format becomes `cpxy.z_os_cpu`. This is a temporary measure until we get a better API for defining target platforms. Summary: - [x] test `select_whls` function needs to be tested to ensure that the whl selection is not impacted when we have the full version in the target platform. - [ ] `download_only` legacy whl code path in `bzlmod` needs further testing. - [x] test `whl_config_setting` handling and config setting creation. The config settings in the hub repo should not use the full version, because from the outside, the whl is compatible with all `micro` versions of a given `3.<minor_version>` of the Python interpreter. This means that the already documented config setting do not need to be changed. - [x] changelog. - [x] `pep508_deps` tests for handling the `full_python_version` correctly. Fixes #2319
1 parent 1d69ad6 commit fd89eca

15 files changed

+92
-31
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ END_UNRELEASED_TEMPLATE
6969
* The `sys._base_executable` value will reflect the underlying interpreter,
7070
not venv interpreter.
7171
* The {obj}`//python/runtime_env_toolchains:all` toolchain now works with it.
72+
* (pypi) Correctly handle `METADATA` entries when `python_full_version` is used in
73+
the environment marker.
74+
Fixes [#2319](https://github.com/bazel-contrib/rules_python/issues/2319).
7275

7376
{#v0-0-0-added}
7477
### Added

python/private/pypi/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ bzl_library(
103103
"//python/private:version_label_bzl",
104104
"@bazel_features//:features",
105105
"@pythons_hub//:interpreters_bzl",
106+
"@pythons_hub//:versions_bzl",
106107
],
107108
)
108109

python/private/pypi/config_settings.bzl

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ specialized is as follows:
4242
* `:is_cp3<minor_version>_abi3_<platform_suffix>`
4343
* `:is_cp3<minor_version>_cp3<minor_version>_<platform_suffix>` and `:is_cp3<minor_version>_cp3<minor_version>t_<platform_suffix>`
4444
45+
Optionally instead of `<minor_version>` there sometimes may be `<minor_version>.<micro_version>` used in order to fully specify the versions
46+
4547
The specialization of free-threaded vs non-free-threaded wheels is the same as
4648
they are just variants of each other. The same goes for the specialization of
4749
`musllinux` vs `manylinux`.

python/private/pypi/extension.bzl

+7-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
load("@bazel_features//:features.bzl", "bazel_features")
1818
load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS")
19+
load("@pythons_hub//:versions.bzl", "MINOR_MAPPING")
1920
load("//python/private:auth.bzl", "AUTH_ATTRS")
21+
load("//python/private:full_version.bzl", "full_version")
2022
load("//python/private:normalize_name.bzl", "normalize_name")
2123
load("//python/private:repo_utils.bzl", "repo_utils")
2224
load("//python/private:semver.bzl", "semver")
@@ -68,6 +70,7 @@ def _create_whl_repos(
6870
pip_attr,
6971
whl_overrides,
7072
available_interpreters = INTERPRETER_LABELS,
73+
minor_mapping = MINOR_MAPPING,
7174
get_index_urls = None):
7275
"""create all of the whl repositories
7376
@@ -159,8 +162,10 @@ def _create_whl_repos(
159162
requirements_osx = pip_attr.requirements_darwin,
160163
requirements_windows = pip_attr.requirements_windows,
161164
extra_pip_args = pip_attr.extra_pip_args,
162-
# TODO @aignas 2025-04-15: pass the full version into here
163-
python_version = major_minor,
165+
python_version = full_version(
166+
version = pip_attr.python_version,
167+
minor_mapping = minor_mapping,
168+
),
164169
logger = logger,
165170
),
166171
extra_pip_args = pip_attr.extra_pip_args,
@@ -304,9 +309,6 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
304309
if requirement.extra_pip_args:
305310
args["extra_pip_args"] = requirement.extra_pip_args
306311

307-
if download_only:
308-
args.setdefault("experimental_target_platforms", requirement.target_platforms)
309-
310312
target_platforms = requirement.target_platforms if multiple_requirements_for_whl else []
311313
repo_name = pypi_repo_name(
312314
normalize_name(requirement.distribution),

python/private/pypi/pkg_aliases.bzl

+3
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ def get_filename_config_settings(
371371

372372
abi = parsed.abi_tag
373373

374+
# TODO @aignas 2025-04-20: test
375+
abi, _, _ = abi.partition(".")
376+
374377
if parsed.platform_tag == "any":
375378
prefixes = ["{}{}_any".format(py, abi)]
376379
else:

python/private/pypi/render_pkg_aliases.bzl

+13-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ def render_pkg_aliases(*, aliases, requirement_cycles = None, extra_hub_aliases
143143
files["_groups/BUILD.bazel"] = generate_group_library_build_bazel("", requirement_cycles)
144144
return files
145145

146+
def _major_minor(python_version):
147+
major, _, tail = python_version.partition(".")
148+
minor, _, _ = tail.partition(".")
149+
return "{}.{}".format(major, minor)
150+
151+
def _major_minor_versions(python_versions):
152+
if not python_versions:
153+
return []
154+
155+
# Use a dict as a simple set
156+
return sorted({_major_minor(v): None for v in python_versions})
157+
146158
def render_multiplatform_pkg_aliases(*, aliases, **kwargs):
147159
"""Render the multi-platform pkg aliases.
148160
@@ -174,7 +186,7 @@ def render_multiplatform_pkg_aliases(*, aliases, **kwargs):
174186
glibc_versions = flag_versions.get("glibc_versions", []),
175187
muslc_versions = flag_versions.get("muslc_versions", []),
176188
osx_versions = flag_versions.get("osx_versions", []),
177-
python_versions = flag_versions.get("python_versions", []),
189+
python_versions = _major_minor_versions(flag_versions.get("python_versions", [])),
178190
target_platforms = flag_versions.get("target_platforms", []),
179191
visibility = ["//:__subpackages__"],
180192
)

python/private/pypi/requirements_files_by_platform.bzl

+3-4
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,12 @@ def _platforms_from_args(extra_pip_args):
9191
return list(platforms.keys())
9292

9393
def _platform(platform_string, python_version = None):
94-
if not python_version or platform_string.startswith("cp3"):
94+
if not python_version or platform_string.startswith("cp"):
9595
return platform_string
9696

97-
_, _, tail = python_version.partition(".")
98-
minor, _, _ = tail.partition(".")
97+
major, _, tail = python_version.partition(".")
9998

100-
return "cp3{}_{}".format(minor, platform_string)
99+
return "cp{}{}_{}".format(major, tail, platform_string)
101100

102101
def requirements_files_by_platform(
103102
*,

python/private/pypi/whl_config_setting.bzl

+11-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,20 @@ def whl_config_setting(*, version = None, config_setting = None, filename = None
3535
a struct with the validated and parsed values.
3636
"""
3737
if target_platforms:
38-
for p in target_platforms:
38+
target_platforms_input = target_platforms
39+
target_platforms = []
40+
for p in target_platforms_input:
3941
if not p.startswith("cp"):
4042
fail("target_platform should start with 'cp' denoting the python version, got: " + p)
4143

44+
abi, _, tail = p.partition("_")
45+
46+
# drop the micro version here, currently there is no usecase to use
47+
# multiple python interpreters with the same minor version but
48+
# different micro version.
49+
abi, _, _ = abi.partition(".")
50+
target_platforms.append("{}_{}".format(abi, tail))
51+
4252
return struct(
4353
config_setting = config_setting,
4454
filename = filename,

python/private/pypi/whl_library_targets.bzl

+6-10
Original file line numberDiff line numberDiff line change
@@ -369,26 +369,22 @@ def _config_settings(dependencies_by_platform, native = native, **kwargs):
369369
if p.startswith("@") or p.endswith("default"):
370370
continue
371371

372+
# TODO @aignas 2025-04-20: add tests here
372373
abi, _, tail = p.partition("_")
373374
if not abi.startswith("cp"):
374375
tail = p
375376
abi = ""
376-
377377
os, _, arch = tail.partition("_")
378-
os = "" if os == "anyos" else os
379-
arch = "" if arch == "anyarch" else arch
380378

381379
_kwargs = dict(kwargs)
382-
if arch:
383-
_kwargs.setdefault("constraint_values", []).append("@platforms//cpu:{}".format(arch))
384-
if os:
385-
_kwargs.setdefault("constraint_values", []).append("@platforms//os:{}".format(os))
380+
_kwargs["constraint_values"] = [
381+
"@platforms//cpu:{}".format(arch),
382+
"@platforms//os:{}".format(os),
383+
]
386384

387385
if abi:
388386
_kwargs["flag_values"] = {
389-
"@rules_python//python/config_settings:python_version_major_minor": "3.{minor_version}".format(
390-
minor_version = abi[len("cp3"):],
391-
),
387+
str(Label("//python/config_settings:python_version")): "3.{}".format(abi[len("cp3"):])
392388
}
393389

394390
native.config_setting(

python/private/pypi/whl_target_platforms.bzl

+4-1
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,11 @@ def select_whls(*, whls, want_platforms = [], logger = None):
7575
fail("expected all platforms to start with ABI, but got: {}".format(p))
7676

7777
abi, _, os_cpu = p.partition("_")
78+
abi, _, _ = abi.partition(".")
7879
_want_platforms[os_cpu] = None
79-
_want_platforms[p] = None
80+
81+
# TODO @aignas 2025-04-20: add a test
82+
_want_platforms["{}_{}".format(abi, os_cpu)] = None
8083

8184
version_limit_candidate = int(abi[3:])
8285
if not version_limit:

tests/pypi/config_settings/config_settings_tests.bzl

+1-1
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ def config_settings_test_suite(name): # buildifier: disable=function-docstring
653653

654654
config_settings(
655655
name = "dummy",
656-
python_versions = ["3.7", "3.8", "3.9", "3.10"],
656+
python_versions = ["3.7", "3.8", "3.9", "3.10.9"],
657657
glibc_versions = [(2, 14), (2, 17)],
658658
muslc_versions = [(1, 1)],
659659
osx_versions = [(10, 9), (11, 0)],

tests/pypi/extension/extension_tests.bzl

+9-3
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def _test_simple(env):
157157
available_interpreters = {
158158
"python_3_15_host": "unit_test_interpreter_target",
159159
},
160+
minor_mapping = {"3.15": "3.15.19"},
160161
)
161162

162163
pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
@@ -204,6 +205,7 @@ def _test_simple_multiple_requirements(env):
204205
available_interpreters = {
205206
"python_3_15_host": "unit_test_interpreter_target",
206207
},
208+
minor_mapping = {"3.15": "3.15.19"},
207209
)
208210

209211
pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
@@ -270,6 +272,7 @@ torch==2.4.1 ; platform_machine != 'x86_64' \
270272
available_interpreters = {
271273
"python_3_15_host": "unit_test_interpreter_target",
272274
},
275+
minor_mapping = {"3.15": "3.15.19"},
273276
)
274277

275278
pypi.exposed_packages().contains_exactly({"pypi": ["torch"]})
@@ -392,6 +395,7 @@ torch==2.4.1+cpu ; platform_machine == 'x86_64' \
392395
available_interpreters = {
393396
"python_3_12_host": "unit_test_interpreter_target",
394397
},
398+
minor_mapping = {"3.12": "3.12.19"},
395399
simpleapi_download = mocksimpleapi_download,
396400
)
397401

@@ -515,6 +519,7 @@ simple==0.0.3 \
515519
available_interpreters = {
516520
"python_3_15_host": "unit_test_interpreter_target",
517521
},
522+
minor_mapping = {"3.15": "3.15.19"},
518523
)
519524

520525
pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
@@ -544,23 +549,22 @@ simple==0.0.3 \
544549
"pypi_315_extra": {
545550
"dep_template": "@pypi//{name}:{target}",
546551
"download_only": True,
547-
"experimental_target_platforms": ["cp315_linux_x86_64"],
552+
# TODO @aignas 2025-04-20: ensure that this is in the hub repo
553+
# "experimental_target_platforms": ["cp315_linux_x86_64"],
548554
"extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
549555
"python_interpreter_target": "unit_test_interpreter_target",
550556
"requirement": "extra==0.0.1 --hash=sha256:deadb00f",
551557
},
552558
"pypi_315_simple_linux_x86_64": {
553559
"dep_template": "@pypi//{name}:{target}",
554560
"download_only": True,
555-
"experimental_target_platforms": ["cp315_linux_x86_64"],
556561
"extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
557562
"python_interpreter_target": "unit_test_interpreter_target",
558563
"requirement": "simple==0.0.1 --hash=sha256:deadbeef",
559564
},
560565
"pypi_315_simple_osx_aarch64": {
561566
"dep_template": "@pypi//{name}:{target}",
562567
"download_only": True,
563-
"experimental_target_platforms": ["cp315_osx_aarch64"],
564568
"extra_pip_args": ["--platform=macosx_10_9_arm64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
565569
"python_interpreter_target": "unit_test_interpreter_target",
566570
"requirement": "simple==0.0.3 --hash=sha256:deadbaaf",
@@ -648,6 +652,7 @@ git_dep @ git+https://git.server/repo/project@deadbeefdeadbeef
648652
available_interpreters = {
649653
"python_3_15_host": "unit_test_interpreter_target",
650654
},
655+
minor_mapping = {"3.15": "3.15.19"},
651656
simpleapi_download = mocksimpleapi_download,
652657
)
653658

@@ -850,6 +855,7 @@ optimum[onnxruntime-gpu]==1.17.1 ; sys_platform == 'linux'
850855
available_interpreters = {
851856
"python_3_15_host": "unit_test_interpreter_target",
852857
},
858+
minor_mapping = {"3.15": "3.15.19"},
853859
)
854860

855861
pypi.exposed_packages().contains_exactly({"pypi": []})

tests/pypi/pep508/deps_tests.bzl

+8-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ _tests.append(test_self_dependencies_can_come_in_any_order)
154154
def _test_can_get_deps_based_on_specific_python_version(env):
155155
requires_dist = [
156156
"bar",
157-
"baz; python_version < '3.8'",
157+
"baz; python_full_version < '3.7.3'",
158158
"posix_dep; os_name=='posix' and python_version >= '3.8'",
159159
]
160160

@@ -163,6 +163,11 @@ def _test_can_get_deps_based_on_specific_python_version(env):
163163
requires_dist = requires_dist,
164164
platforms = ["cp38_linux_x86_64"],
165165
)
166+
py373 = deps(
167+
"foo",
168+
requires_dist = requires_dist,
169+
platforms = ["cp37.3_linux_x86_64"],
170+
)
166171
py37 = deps(
167172
"foo",
168173
requires_dist = requires_dist,
@@ -174,6 +179,8 @@ def _test_can_get_deps_based_on_specific_python_version(env):
174179
env.expect.that_dict(py37.deps_select).contains_exactly({})
175180
env.expect.that_collection(py38.deps).contains_exactly(["bar", "posix_dep"])
176181
env.expect.that_dict(py38.deps_select).contains_exactly({})
182+
env.expect.that_collection(py373.deps).contains_exactly(["bar"])
183+
env.expect.that_dict(py373.deps_select).contains_exactly({})
177184

178185
_tests.append(_test_can_get_deps_based_on_specific_python_version)
179186

tests/pypi/render_pkg_aliases/render_pkg_aliases_test.bzl

+5-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ def _test_bzlmod_aliases(env):
6868
aliases = {
6969
"bar-baz": {
7070
whl_config_setting(
71-
version = "3.2",
71+
# Add one with micro version to mimic construction in the extension
72+
version = "3.2.2",
7273
config_setting = "//:my_config_setting",
7374
): "pypi_32_bar_baz",
7475
whl_config_setting(
@@ -83,10 +84,10 @@ def _test_bzlmod_aliases(env):
8384
filename = "foo-0.0.0-py3-none-any.whl",
8485
): "filename_repo",
8586
whl_config_setting(
86-
version = "3.2",
87+
version = "3.2.2",
8788
filename = "foo-0.0.0-py3-none-any.whl",
8889
target_platforms = [
89-
"cp32_linux_x86_64",
90+
"cp32.2_linux_x86_64",
9091
],
9192
): "filename_repo_linux_x86_64",
9293
},
@@ -117,7 +118,7 @@ pkg_aliases(
117118
whl_config_setting(
118119
filename = "foo-0.0.0-py3-none-any.whl",
119120
target_platforms = ("cp32_linux_x86_64",),
120-
version = "3.2",
121+
version = "3.2.2",
121122
): "filename_repo_linux_x86_64",
122123
},
123124
extra_aliases = ["foo"],

tests/pypi/whl_target_platforms/select_whl_tests.bzl

+16
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,22 @@ def _test_freethreaded_wheels(env):
289289

290290
_tests.append(_test_freethreaded_wheels)
291291

292+
def _test_micro_version_freethreaded(env):
293+
# Check we prefer platform specific wheels
294+
got = _select_whls(whls = WHL_LIST, want_platforms = ["cp313.3_linux_x86_64"])
295+
_match(
296+
env,
297+
got,
298+
"pkg-0.0.1-cp313-cp313t-musllinux_1_1_x86_64.whl",
299+
"pkg-0.0.1-cp313-cp313-musllinux_1_1_x86_64.whl",
300+
"pkg-0.0.1-cp313-abi3-musllinux_1_1_x86_64.whl",
301+
"pkg-0.0.1-cp313-none-musllinux_1_1_x86_64.whl",
302+
"pkg-0.0.1-cp39-abi3-any.whl",
303+
"pkg-0.0.1-py3-none-any.whl",
304+
)
305+
306+
_tests.append(_test_micro_version_freethreaded)
307+
292308
def select_whl_test_suite(name):
293309
"""Create the test suite.
294310

0 commit comments

Comments
 (0)