Skip to content

Commit

Permalink
Fix universal lock handling of the none ABI. (#2270)
Browse files Browse the repository at this point in the history
The `none` ABI indicates no native ABI attachment; and so just the
standard guarantees of pure Python. In general this means no breaks
within a major version (just fixes and additions). Although it's known
CPython does deal breaks within a major version span (some deprecated
items are removed), the de-facto standard presented by PyPA's packaging
library indicate the `none` ABI is compatible for all minor versions in
the major version range (much like the `abi3` ABI for Python 3). As
such, fix universal lock handling to accept a `*<major>*-none-*.whl` for
all minor versions of Python `<major>`.

Fixes #2268
  • Loading branch information
jsirois authored Nov 3, 2023
1 parent a43c48c commit baef9ea
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
6 changes: 4 additions & 2 deletions pex/resolve/locker_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def patch_wheel_model():
def supported_version(self, *_args, **_kwargs):
if not hasattr(self, "_versions"):
versions = set()
is_abi3 = ["abi3"] == list(self.abis)
abis = list(self.abis)
is_abi3 = ["abi3"] == abis
is_abi_none = ["none"] == abis
for pyversion in self.pyversions:
# For the format, see: https://peps.python.org/pep-0425/#python-tag
match = re.search(r"^(?P<impl>\D{2,})(?P<major>\d)(?P<minor>\d+)?", pyversion)
Expand All @@ -106,7 +108,7 @@ def supported_version(self, *_args, **_kwargs):

major = int(match.group("major"))
minor = match.group("minor")
if is_abi3 and major == 3:
if is_abi_none or (is_abi3 and major == 3):
versions.add(major)
elif minor:
versions.add((major, int(minor)))
Expand Down
74 changes: 74 additions & 0 deletions tests/integration/cli/commands/test_issue_2268.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import os.path
import sys

import pytest

from pex.compatibility import commonpath
from pex.resolve.resolver_configuration import ResolverVersion
from pex.typing import TYPE_CHECKING
from testing import run_pex_command
from testing.cli import run_pex3

if TYPE_CHECKING:
from typing import Any


@pytest.mark.skipif(
sys.version_info[:2] < (3, 5),
reason=(
"This test relies on the python-forge 18.6.0 offering only the single "
"python_forge-18.6.0-py35-none-any.whl distribution on PyPI and this wheel only works for "
"Python 3.5 and greater"
),
)
@pytest.mark.parametrize(
"resolver_version",
[
pytest.param(resolver_version, id=str(resolver_version))
for resolver_version in ResolverVersion.values()
if ResolverVersion.applies(resolver_version)
],
)
def test_abi_none_locking(
tmpdir, # type: Any
resolver_version, # type: ResolverVersion.Value
):
# type: (...) -> None

pex_root = os.path.join(str(tmpdir), "pex_root")
lock = os.path.join(str(tmpdir), "lock.json")
run_pex3(
"lock",
"create",
"--style",
"universal",
"--resolver-version",
str(resolver_version),
"--interpreter-constraint",
"==3.11.*",
"python-forge==18.6.0",
"-o",
lock,
"--indent",
"2",
"--pex-root",
pex_root,
).assert_success()

result = run_pex_command(
args=[
"--pex-root",
pex_root,
"--runtime-pex-root",
pex_root,
"--lock",
lock,
"--",
"-c",
"import forge; print(forge.__file__)",
]
)
result.assert_success()
assert pex_root == commonpath([pex_root, result.output.strip()])
6 changes: 4 additions & 2 deletions tests/integration/test_issue_539.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from pex.common import temporary_dir
from pex.pip.installation import get_pip
from pex.resolve.configured_resolver import ConfiguredResolver
from testing import IS_LINUX_ARM64, IS_PYPY, PY_VER, run_pex_command


Expand All @@ -35,11 +36,12 @@ def test_abi3_resolution():
# sdist. Since we want to test in --no-build, we pre-resolve/build the pycparser wheel here and
# add the resulting wheelhouse to the --no-build pex command.
download_dir = os.path.join(td, ".downloads")
get_pip().spawn_download_distributions(
pip = get_pip(resolver=ConfiguredResolver.default())
pip.spawn_download_distributions(
download_dir=download_dir, requirements=["pycparser"]
).wait()
wheel_dir = os.path.join(td, ".wheels")
get_pip().spawn_build_wheels(
pip.spawn_build_wheels(
wheel_dir=wheel_dir, distributions=glob.glob(os.path.join(download_dir, "*"))
).wait()

Expand Down

0 comments on commit baef9ea

Please sign in to comment.