Skip to content

Use packaging.tags for doing compatible wheel tag calculation #7354

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 34 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3ada01f
Convert return values in pep425tags.get_supported
chrahunt Nov 23, 2019
d386bb2
Copy get_supported into packaging.tags-like functions
chrahunt Nov 23, 2019
54db17c
Use _cpython_tags, _generic_tags, and _compatible_tags
chrahunt Nov 23, 2019
1c8c481
Only calculate py-compatible tags in one place
chrahunt Nov 23, 2019
480911b
Remove unused abi arg from _compatible_tags
chrahunt Nov 23, 2019
4659a78
Use packaging.tags.compatible_tags
chrahunt Nov 23, 2019
750abca
Customize python_version for packaging.tags.compatible_tags
chrahunt Nov 23, 2019
72d00dd
Customize interpreter for packaging.tags.compatible_tags
chrahunt Nov 23, 2019
2de0b7c
Customize platforms for packaging.tags.compatible_tags
chrahunt Nov 23, 2019
c514c6b
Make packaging.tags.compatible_tags unconditional
chrahunt Nov 23, 2019
b91286c
Inline packaging.tags.compatible_tags
chrahunt Nov 23, 2019
8f1c60e
Only use _cpython_tags for CPython
chrahunt Nov 23, 2019
e388df6
Remove impl from _cpython_tags
chrahunt Nov 23, 2019
5dbef5d
Use packaging.tags.cpython_tags
chrahunt Nov 23, 2019
147680a
Customize python_version for packaging.tags.cpython_tags
chrahunt Nov 23, 2019
05045e7
Customize abis for packaging.tags.cpython_tags
chrahunt Nov 23, 2019
fecfadb
Customize platforms for packaging.tags.cpython_tags
chrahunt Nov 23, 2019
56840c3
Make packaging.tags.cpython_tags unconditional
chrahunt Nov 23, 2019
1574872
Inline packaging.tags.cpython_tags
chrahunt Nov 23, 2019
fa1ec40
Remove unused abi3 branch in _generic_tags
chrahunt Nov 23, 2019
281273d
Use packaging.tags.generic_tags
chrahunt Nov 23, 2019
77dbd27
Customize interpreter for packaging.tags.generic_tags
chrahunt Nov 23, 2019
0bebeb6
Customize abis for packaging.tags.generic_tags
chrahunt Nov 23, 2019
293b778
Customize platforms for packaging.tags.generic_tags
chrahunt Nov 23, 2019
72dcd34
Make packaging.tags.generic_tags unconditional
chrahunt Nov 23, 2019
3e66ab0
Inline packaging.tags.generic_tags
chrahunt Nov 23, 2019
ad546b5
Remove unnecessary conversion in get_supported
chrahunt Nov 23, 2019
9b34435
Simplify _get_custom_platforms
chrahunt Nov 23, 2019
2455977
Remove unused manylinux auto-deduction functions
chrahunt Nov 23, 2019
7aaa705
Remove unused glibc functions
chrahunt Nov 23, 2019
896317d
Remove unused abi functions
chrahunt Nov 23, 2019
2b1b60f
Remove unused get_platform function
chrahunt Nov 23, 2019
ae21af7
Remove unused version functions
chrahunt Nov 23, 2019
d7fda71
Add news
chrahunt Nov 23, 2019
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
2 changes: 2 additions & 0 deletions news/6908.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove wheel tag calculation from pip and use ``packaging.tags``. This
should provide more tags ordered better than in prior releases.
334 changes: 65 additions & 269 deletions src/pip/_internal/pep425tags.py
Original file line number Diff line number Diff line change
@@ -1,235 +1,37 @@
"""Generate and work with PEP 425 Compatibility Tags."""
from __future__ import absolute_import

import distutils.util
import logging
import platform
import re
import sys
import sysconfig

from pip._vendor.packaging.tags import (
Tag,
compatible_tags,
cpython_tags,
generic_tags,
interpreter_name,
interpreter_version,
mac_platforms,
)
from pip._vendor.six import PY2

import pip._internal.utils.glibc
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
from typing import (
Callable, List, Optional, Tuple, Union
)
from typing import List, Optional, Tuple

from pip._vendor.packaging.tags import PythonVersion

logger = logging.getLogger(__name__)

_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')


def get_config_var(var):
# type: (str) -> Optional[str]
return sysconfig.get_config_var(var)


def version_info_to_nodot(version_info):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow up: I suggest moving the call to be changed -- changing get_supported to allow for version to be None (and use this value in that case).

# type: (Tuple[int, ...]) -> str
# Only use up to the first two numbers.
return ''.join(map(str, version_info[:2]))


def get_impl_version_info():
# type: () -> Tuple[int, ...]
"""Return sys.version_info-like tuple for use in decrementing the minor
version."""
if interpreter_name() == 'pp':
# as per https://github.com/pypa/pip/issues/2882
# attrs exist only on pypy
return (sys.version_info[0],
sys.pypy_version_info.major, # type: ignore
sys.pypy_version_info.minor) # type: ignore
else:
return sys.version_info[0], sys.version_info[1]


def get_flag(var, fallback, expected=True, warn=True):
# type: (str, Callable[..., bool], Union[bool, int], bool) -> bool
"""Use a fallback method for determining SOABI flags if the needed config
var is unset or unavailable."""
val = get_config_var(var)
if val is None:
if warn:
logger.debug("Config variable '%s' is unset, Python ABI tag may "
"be incorrect", var)
return fallback()
return val == expected


def get_abi_tag():
# type: () -> Optional[str]
"""Return the ABI tag based on SOABI (if available) or emulate SOABI
(CPython 2, PyPy)."""
soabi = get_config_var('SOABI')
impl = interpreter_name()
abi = None # type: Optional[str]

if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
d = ''
m = ''
u = ''
is_cpython = (impl == 'cp')
if get_flag(
'Py_DEBUG', lambda: hasattr(sys, 'gettotalrefcount'),
warn=is_cpython):
d = 'd'
if sys.version_info < (3, 8) and get_flag(
'WITH_PYMALLOC', lambda: is_cpython, warn=is_cpython):
m = 'm'
if sys.version_info < (3, 3) and get_flag(
'Py_UNICODE_SIZE', lambda: sys.maxunicode == 0x10ffff,
expected=4, warn=is_cpython):
u = 'u'
abi = '%s%s%s%s%s' % (impl, interpreter_version(), d, m, u)
elif soabi and soabi.startswith('cpython-'):
abi = 'cp' + soabi.split('-')[1]
elif soabi:
abi = soabi.replace('.', '_').replace('-', '_')

return abi


def _is_running_32bit():
# type: () -> bool
return sys.maxsize == 2147483647


def get_platform():
# type: () -> str
"""Return our platform name 'win32', 'linux_x86_64'"""
if sys.platform == 'darwin':
# distutils.util.get_platform() returns the release based on the value
# of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may
# be significantly older than the user's current machine.
release, _, machine = platform.mac_ver()
split_ver = release.split('.')

if machine == "x86_64" and _is_running_32bit():
machine = "i386"
elif machine == "ppc64" and _is_running_32bit():
machine = "ppc"

return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)

# XXX remove distutils dependency
result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
if result == "linux_x86_64" and _is_running_32bit():
# 32 bit Python program (running on a 64 bit Linux): pip should only
# install and run 32 bit compiled extensions in that case.
result = "linux_i686"

return result


def is_linux_armhf():
# type: () -> bool
if get_platform() != "linux_armv7l":
return False
# hard-float ABI can be detected from the ELF header of the running
# process
try:
with open(sys.executable, 'rb') as f:
elf_header_raw = f.read(40) # read 40 first bytes of ELF header
except (IOError, OSError, TypeError):
return False
if elf_header_raw is None or len(elf_header_raw) < 40:
return False
if isinstance(elf_header_raw, str):
elf_header = [ord(c) for c in elf_header_raw]
else:
elf_header = [b for b in elf_header_raw]
result = elf_header[0:4] == [0x7f, 0x45, 0x4c, 0x46] # ELF magic number
result &= elf_header[4:5] == [1] # 32-bit ELF
result &= elf_header[5:6] == [1] # little-endian
result &= elf_header[18:20] == [0x28, 0] # ARM machine
result &= elf_header[39:40] == [5] # ARM EABIv5
result &= (elf_header[37:38][0] & 4) == 4 # EF_ARM_ABI_FLOAT_HARD
return result


def is_manylinux1_compatible():
# type: () -> bool
# Only Linux, and only x86-64 / i686
if get_platform() not in {"linux_x86_64", "linux_i686"}:
return False

# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux1_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass

# Check glibc version. CentOS 5 uses glibc 2.5.
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)


def is_manylinux2010_compatible():
# type: () -> bool
# Only Linux, and only x86-64 / i686
if get_platform() not in {"linux_x86_64", "linux_i686"}:
return False

# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux2010_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass

# Check glibc version. CentOS 6 uses glibc 2.12.
return pip._internal.utils.glibc.have_compatible_glibc(2, 12)


def is_manylinux2014_compatible():
# type: () -> bool
# Only Linux, and only supported architectures
platform = get_platform()
if platform not in {"linux_x86_64", "linux_i686", "linux_aarch64",
"linux_armv7l", "linux_ppc64", "linux_ppc64le",
"linux_s390x"}:
return False

# check for hard-float ABI in case we're running linux_armv7l not to
# install hard-float ABI wheel in a soft-float ABI environment
if platform == "linux_armv7l" and not is_linux_armhf():
return False

# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux2014_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass

# Check glibc version. CentOS 7 uses glibc 2.17.
return pip._internal.utils.glibc.have_compatible_glibc(2, 17)


def get_all_minor_versions_as_strings(version_info):
# type: (Tuple[int, ...]) -> List[str]
versions = []
major = version_info[:-1]
# Support all previous minor Python versions.
for minor in range(version_info[-1], -1, -1):
versions.append(''.join(map(str, major + (minor,))))
return versions


def _mac_platforms(arch):
# type: (str) -> List[str]
match = _osx_arch_pat.match(arch)
Expand Down Expand Up @@ -273,27 +75,35 @@ def _custom_manylinux_platforms(arch):
return arches


def _get_custom_platforms(arch, platform):
# type: (str, Optional[str]) -> List[str]
def _get_custom_platforms(arch):
# type: (str) -> List[str]
arch_prefix, arch_sep, arch_suffix = arch.partition('_')
if arch.startswith('macosx'):
arches = _mac_platforms(arch)
elif arch_prefix in ['manylinux2014', 'manylinux2010']:
arches = _custom_manylinux_platforms(arch)
elif platform is None:
arches = []
if is_manylinux2014_compatible():
arches.append('manylinux2014' + arch_sep + arch_suffix)
if is_manylinux2010_compatible():
arches.append('manylinux2010' + arch_sep + arch_suffix)
if is_manylinux1_compatible():
arches.append('manylinux1' + arch_sep + arch_suffix)
arches.append(arch)
else:
arches = [arch]
return arches


def _get_python_version(version):
# type: (str) -> PythonVersion
if len(version) > 1:
return int(version[0]), int(version[1:])
else:
return (int(version[0]),)


def _get_custom_interpreter(implementation=None, version=None):
# type: (Optional[str], Optional[str]) -> str
if implementation is None:
implementation = interpreter_name()
if version is None:
version = interpreter_version()
return "{}{}".format(implementation, version)


def get_supported(
version=None, # type: Optional[str]
platform=None, # type: Optional[str]
Expand All @@ -313,59 +123,45 @@ def get_supported(
:param abi: specify the exact abi you want valid
tags for, or None. If None, use the local interpreter abi.
"""
supported = []

# Versions must be given with respect to the preference
if version is None:
version_info = get_impl_version_info()
versions = get_all_minor_versions_as_strings(version_info)
supported = [] # type: List[Tag]

python_version = None # type: Optional[PythonVersion]
if version is not None:
python_version = _get_python_version(version)

interpreter = _get_custom_interpreter(impl, version)

abis = None # type: Optional[List[str]]
if abi is not None:
abis = [abi]

platforms = None # type: Optional[List[str]]
if platform is not None:
platforms = _get_custom_platforms(platform)

is_cpython = (impl or interpreter_name()) == "cp"
if is_cpython:
supported.extend(
cpython_tags(
python_version=python_version,
abis=abis,
platforms=platforms,
)
)
else:
versions = [version]
current_version = versions[0]
other_versions = versions[1:]

impl = impl or interpreter_name()

abis = [] # type: List[str]

abi = abi or get_abi_tag()
if abi:
abis[0:0] = [abi]

supports_abi3 = not PY2 and impl == "cp"

if supports_abi3:
abis.append("abi3")

abis.append('none')

arches = _get_custom_platforms(platform or get_platform(), platform)

# Current version, current API (built specifically for our Python):
for abi in abis:
for arch in arches:
supported.append(('%s%s' % (impl, current_version), abi, arch))

# abi3 modules compatible with older version of Python
if supports_abi3:
for version in other_versions:
# abi3 was introduced in Python 3.2
if version in {'31', '30'}:
break
for arch in arches:
supported.append(("%s%s" % (impl, version), "abi3", arch))

# Has binaries, does not use the Python API:
for arch in arches:
supported.append(('py%s' % (current_version[0]), 'none', arch))

# No abi / arch, but requires our implementation:
supported.append(('%s%s' % (impl, current_version), 'none', 'any'))

# No abi / arch, generic Python
supported.append(('py%s' % (current_version,), 'none', 'any'))
supported.append(('py%s' % (current_version[0]), 'none', 'any'))
for version in other_versions:
supported.append(('py%s' % (version,), 'none', 'any'))
supported.extend(
generic_tags(
interpreter=interpreter,
abis=abis,
platforms=platforms,
)
)
supported.extend(
compatible_tags(
python_version=python_version,
interpreter=interpreter,
platforms=platforms,
)
)

return [Tag(*parts) for parts in supported]
return supported
Loading