Skip to content

fix: use the python micro version to parse whl metadata in bzlmod #2793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
# To update these lines, execute
# `bazel run @rules_bazel_integration_test//tools:update_deleted_packages`
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered,tests/modules/other,tests/modules/other/nspkg_delta,tests/modules/other/nspkg_gamma

test --test_output=errors

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ END_UNRELEASED_TEMPLATE
* (packaging) An empty `requires_file` is treated as if it were omitted, resulting in a valid `METADATA` file.
* (rules) py_wheel and sphinxdocs rules now propagate `target_compatible_with` to all targets they create.
[PR #2788](https://github.com/bazel-contrib/rules_python/pull/2788).
* (pypi) Correctly handle `METADATA` entries when `python_full_version` is used in
the environment marker.
Fixes [#2319](https://github.com/bazel-contrib/rules_python/issues/2319).

{#1-4-0-added}
### Added
Expand Down
8 changes: 4 additions & 4 deletions examples/bzlmod/entry_points/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
load("@python_versions//3.9:defs.bzl", py_console_script_binary_3_9 = "py_console_script_binary")
load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")

# This is how you can define a `pylint` entrypoint which uses the default python version.
Expand All @@ -24,10 +23,11 @@ py_console_script_binary(
],
)

# A specific Python version can be forced by using the generated version-aware
# wrappers, e.g. to force Python 3.9:
py_console_script_binary_3_9(
# A specific Python version can be forced by passing `python_version`
# attribute, e.g. to force Python 3.9:
py_console_script_binary(
name = "yamllint",
pkg = "@pip//yamllint:pkg",
python_version = "3.9",
visibility = ["//entry_points:__subpackages__"],
)
3 changes: 3 additions & 0 deletions python/private/pypi/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ bzl_library(
"//python/private:version_label_bzl",
"@bazel_features//:features",
"@pythons_hub//:interpreters_bzl",
"@pythons_hub//:versions_bzl",
],
)

Expand Down Expand Up @@ -220,7 +221,9 @@ bzl_library(
":pep508_evaluate_bzl",
":pep508_platform_bzl",
":pep508_requirement_bzl",
"//python/private:full_version_bzl",
"//python/private:normalize_name_bzl",
"@pythons_hub//:versions_bzl",
],
)

Expand Down
2 changes: 2 additions & 0 deletions python/private/pypi/config_settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ specialized is as follows:
* `:is_cp3<minor_version>_abi3_<platform_suffix>`
* `:is_cp3<minor_version>_cp3<minor_version>_<platform_suffix>` and `:is_cp3<minor_version>_cp3<minor_version>t_<platform_suffix>`

Optionally instead of `<minor_version>` there sometimes may be `<minor_version>.<micro_version>` used in order to fully specify the versions

The specialization of free-threaded vs non-free-threaded wheels is the same as
they are just variants of each other. The same goes for the specialization of
`musllinux` vs `manylinux`.
Expand Down
14 changes: 9 additions & 5 deletions python/private/pypi/extension.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

load("@bazel_features//:features.bzl", "bazel_features")
load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS")
load("@pythons_hub//:versions.bzl", "MINOR_MAPPING")
load("//python/private:auth.bzl", "AUTH_ATTRS")
load("//python/private:full_version.bzl", "full_version")
load("//python/private:normalize_name.bzl", "normalize_name")
load("//python/private:repo_utils.bzl", "repo_utils")
load("//python/private:semver.bzl", "semver")
Expand Down Expand Up @@ -68,6 +70,7 @@ def _create_whl_repos(
pip_attr,
whl_overrides,
available_interpreters = INTERPRETER_LABELS,
minor_mapping = MINOR_MAPPING,
get_index_urls = None):
"""create all of the whl repositories

Expand All @@ -80,6 +83,8 @@ def _create_whl_repos(
interpreters that have been registered using the `python` bzlmod extension.
The keys are in the form `python_{snake_case_version}_host`. This is to be
used during the `repository_rule` and must be always compatible with the host.
minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full
python version used to parse package METADATA files.

Returns a {type}`struct` with the following attributes:
whl_map: {type}`dict[str, list[struct]]` the output is keyed by the
Expand Down Expand Up @@ -159,8 +164,10 @@ def _create_whl_repos(
requirements_osx = pip_attr.requirements_darwin,
requirements_windows = pip_attr.requirements_windows,
extra_pip_args = pip_attr.extra_pip_args,
# TODO @aignas 2025-04-15: pass the full version into here
python_version = major_minor,
python_version = full_version(
version = pip_attr.python_version,
minor_mapping = minor_mapping,
),
logger = logger,
),
extra_pip_args = pip_attr.extra_pip_args,
Expand Down Expand Up @@ -304,9 +311,6 @@ def _whl_repos(*, requirement, whl_library_args, download_only, netrc, auth_patt
if requirement.extra_pip_args:
args["extra_pip_args"] = requirement.extra_pip_args

if download_only:
args.setdefault("experimental_target_platforms", requirement.target_platforms)

target_platforms = requirement.target_platforms if multiple_requirements_for_whl else []
repo_name = pypi_repo_name(
normalize_name(requirement.distribution),
Expand Down
27 changes: 22 additions & 5 deletions python/private/pypi/pep508_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@
"""This module is for implementing PEP508 compliant METADATA deps parsing.
"""

load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION")
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING")
load("//python/private:full_version.bzl", "full_version")
load("//python/private:normalize_name.bzl", "normalize_name")
load(":pep508_env.bzl", "env")
load(":pep508_evaluate.bzl", "evaluate")
load(":pep508_platform.bzl", "platform", "platform_from_str")
load(":pep508_requirement.bzl", "requirement")

def deps(name, *, requires_dist, platforms = [], extras = [], excludes = [], default_python_version = None):
def deps(
name,
*,
requires_dist,
platforms = [],
extras = [],
excludes = [],
default_python_version = None,
minor_mapping = MINOR_MAPPING):
"""Parse the RequiresDist from wheel METADATA

Args:
Expand All @@ -33,6 +42,9 @@ def deps(name, *, requires_dist, platforms = [], extras = [], excludes = [], def
extras: {type}`list[str]` the requested extras to generate targets for.
platforms: {type}`list[str]` the list of target platform strings.
default_python_version: {type}`str` the host python version.
minor_mapping: {type}`type[str, str]` the minor mapping to use when
resolving to the full python version as DEFAULT_PYTHON_VERSION can by
of format `3.x`.

Returns:
A struct with attributes:
Expand All @@ -53,16 +65,21 @@ def deps(name, *, requires_dist, platforms = [], extras = [], excludes = [], def
excludes = [name] + [normalize_name(x) for x in excludes]

default_python_version = default_python_version or DEFAULT_PYTHON_VERSION
if default_python_version:
# if it is not bzlmod, then DEFAULT_PYTHON_VERSION may be unset
default_python_version = full_version(
version = default_python_version,
minor_mapping = minor_mapping,
)
platforms = [
platform_from_str(p, python_version = default_python_version)
for p in platforms
]

abis = sorted({p.abi: True for p in platforms if p.abi})
if default_python_version and len(abis) > 1:
_, _, minor_version = default_python_version.partition(".")
minor_version, _, _ = minor_version.partition(".")
default_abi = "cp3" + minor_version
_, _, tail = default_python_version.partition(".")
default_abi = "cp3" + tail
elif len(abis) > 1:
fail(
"all python versions need to be specified explicitly, got: {}".format(platforms),
Expand Down
3 changes: 3 additions & 0 deletions python/private/pypi/pkg_aliases.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ def get_filename_config_settings(

abi = parsed.abi_tag

# TODO @aignas 2025-04-20: test
abi, _, _ = abi.partition(".")

if parsed.platform_tag == "any":
prefixes = ["{}{}_any".format(py, abi)]
else:
Expand Down
14 changes: 13 additions & 1 deletion python/private/pypi/render_pkg_aliases.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ def render_pkg_aliases(*, aliases, requirement_cycles = None, extra_hub_aliases
files["_groups/BUILD.bazel"] = generate_group_library_build_bazel("", requirement_cycles)
return files

def _major_minor(python_version):
major, _, tail = python_version.partition(".")
minor, _, _ = tail.partition(".")
return "{}.{}".format(major, minor)

def _major_minor_versions(python_versions):
if not python_versions:
return []

# Use a dict as a simple set
return sorted({_major_minor(v): None for v in python_versions})

def render_multiplatform_pkg_aliases(*, aliases, **kwargs):
"""Render the multi-platform pkg aliases.

Expand Down Expand Up @@ -174,7 +186,7 @@ def render_multiplatform_pkg_aliases(*, aliases, **kwargs):
glibc_versions = flag_versions.get("glibc_versions", []),
muslc_versions = flag_versions.get("muslc_versions", []),
osx_versions = flag_versions.get("osx_versions", []),
python_versions = flag_versions.get("python_versions", []),
python_versions = _major_minor_versions(flag_versions.get("python_versions", [])),
target_platforms = flag_versions.get("target_platforms", []),
visibility = ["//:__subpackages__"],
)
Expand Down
7 changes: 3 additions & 4 deletions python/private/pypi/requirements_files_by_platform.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,12 @@ def _platforms_from_args(extra_pip_args):
return list(platforms.keys())

def _platform(platform_string, python_version = None):
if not python_version or platform_string.startswith("cp3"):
if not python_version or platform_string.startswith("cp"):
return platform_string

_, _, tail = python_version.partition(".")
minor, _, _ = tail.partition(".")
major, _, tail = python_version.partition(".")

return "cp3{}_{}".format(minor, platform_string)
return "cp{}{}_{}".format(major, tail, platform_string)

def requirements_files_by_platform(
*,
Expand Down
12 changes: 11 additions & 1 deletion python/private/pypi/whl_config_setting.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ def whl_config_setting(*, version = None, config_setting = None, filename = None
a struct with the validated and parsed values.
"""
if target_platforms:
for p in target_platforms:
target_platforms_input = target_platforms
target_platforms = []
for p in target_platforms_input:
if not p.startswith("cp"):
fail("target_platform should start with 'cp' denoting the python version, got: " + p)

abi, _, tail = p.partition("_")

# drop the micro version here, currently there is no usecase to use
# multiple python interpreters with the same minor version but
# different micro version.
abi, _, _ = abi.partition(".")
target_platforms.append("{}_{}".format(abi, tail))

return struct(
config_setting = config_setting,
filename = filename,
Expand Down
16 changes: 6 additions & 10 deletions python/private/pypi/whl_library_targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -369,26 +369,22 @@ def _config_settings(dependencies_by_platform, native = native, **kwargs):
if p.startswith("@") or p.endswith("default"):
continue

# TODO @aignas 2025-04-20: add tests here
abi, _, tail = p.partition("_")
if not abi.startswith("cp"):
tail = p
abi = ""

os, _, arch = tail.partition("_")
os = "" if os == "anyos" else os
arch = "" if arch == "anyarch" else arch

_kwargs = dict(kwargs)
if arch:
_kwargs.setdefault("constraint_values", []).append("@platforms//cpu:{}".format(arch))
if os:
_kwargs.setdefault("constraint_values", []).append("@platforms//os:{}".format(os))
_kwargs["constraint_values"] = [
"@platforms//cpu:{}".format(arch),
"@platforms//os:{}".format(os),
]

if abi:
_kwargs["flag_values"] = {
"@rules_python//python/config_settings:python_version_major_minor": "3.{minor_version}".format(
minor_version = abi[len("cp3"):],
),
Label("//python/config_settings:python_version"): "3.{}".format(abi[len("cp3"):]),
}

native.config_setting(
Expand Down
5 changes: 4 additions & 1 deletion python/private/pypi/whl_target_platforms.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ def select_whls(*, whls, want_platforms = [], logger = None):
fail("expected all platforms to start with ABI, but got: {}".format(p))

abi, _, os_cpu = p.partition("_")
abi, _, _ = abi.partition(".")
_want_platforms[os_cpu] = None
_want_platforms[p] = None

# TODO @aignas 2025-04-20: add a test
_want_platforms["{}_{}".format(abi, os_cpu)] = None

version_limit_candidate = int(abi[3:])
if not version_limit:
Expand Down
Loading