Skip to content

Commit fe3e892

Browse files
authored
Merge pull request #7354 from chrahunt/maint/use-packaging-tags
Use packaging.tags for doing compatible wheel tag calculation
2 parents 0eaf0e6 + d7fda71 commit fe3e892

File tree

5 files changed

+70
-540
lines changed

5 files changed

+70
-540
lines changed

news/6908.removal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove wheel tag calculation from pip and use ``packaging.tags``. This
2+
should provide more tags ordered better than in prior releases.

src/pip/_internal/pep425tags.py

Lines changed: 65 additions & 269 deletions
Original file line numberDiff line numberDiff line change
@@ -1,235 +1,37 @@
11
"""Generate and work with PEP 425 Compatibility Tags."""
22
from __future__ import absolute_import
33

4-
import distutils.util
54
import logging
6-
import platform
75
import re
8-
import sys
9-
import sysconfig
106

117
from pip._vendor.packaging.tags import (
128
Tag,
9+
compatible_tags,
10+
cpython_tags,
11+
generic_tags,
1312
interpreter_name,
1413
interpreter_version,
1514
mac_platforms,
1615
)
17-
from pip._vendor.six import PY2
1816

19-
import pip._internal.utils.glibc
2017
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2118

2219
if MYPY_CHECK_RUNNING:
23-
from typing import (
24-
Callable, List, Optional, Tuple, Union
25-
)
20+
from typing import List, Optional, Tuple
21+
22+
from pip._vendor.packaging.tags import PythonVersion
2623

2724
logger = logging.getLogger(__name__)
2825

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

3128

32-
def get_config_var(var):
33-
# type: (str) -> Optional[str]
34-
return sysconfig.get_config_var(var)
35-
36-
3729
def version_info_to_nodot(version_info):
3830
# type: (Tuple[int, ...]) -> str
3931
# Only use up to the first two numbers.
4032
return ''.join(map(str, version_info[:2]))
4133

4234

43-
def get_impl_version_info():
44-
# type: () -> Tuple[int, ...]
45-
"""Return sys.version_info-like tuple for use in decrementing the minor
46-
version."""
47-
if interpreter_name() == 'pp':
48-
# as per https://github.com/pypa/pip/issues/2882
49-
# attrs exist only on pypy
50-
return (sys.version_info[0],
51-
sys.pypy_version_info.major, # type: ignore
52-
sys.pypy_version_info.minor) # type: ignore
53-
else:
54-
return sys.version_info[0], sys.version_info[1]
55-
56-
57-
def get_flag(var, fallback, expected=True, warn=True):
58-
# type: (str, Callable[..., bool], Union[bool, int], bool) -> bool
59-
"""Use a fallback method for determining SOABI flags if the needed config
60-
var is unset or unavailable."""
61-
val = get_config_var(var)
62-
if val is None:
63-
if warn:
64-
logger.debug("Config variable '%s' is unset, Python ABI tag may "
65-
"be incorrect", var)
66-
return fallback()
67-
return val == expected
68-
69-
70-
def get_abi_tag():
71-
# type: () -> Optional[str]
72-
"""Return the ABI tag based on SOABI (if available) or emulate SOABI
73-
(CPython 2, PyPy)."""
74-
soabi = get_config_var('SOABI')
75-
impl = interpreter_name()
76-
abi = None # type: Optional[str]
77-
78-
if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
79-
d = ''
80-
m = ''
81-
u = ''
82-
is_cpython = (impl == 'cp')
83-
if get_flag(
84-
'Py_DEBUG', lambda: hasattr(sys, 'gettotalrefcount'),
85-
warn=is_cpython):
86-
d = 'd'
87-
if sys.version_info < (3, 8) and get_flag(
88-
'WITH_PYMALLOC', lambda: is_cpython, warn=is_cpython):
89-
m = 'm'
90-
if sys.version_info < (3, 3) and get_flag(
91-
'Py_UNICODE_SIZE', lambda: sys.maxunicode == 0x10ffff,
92-
expected=4, warn=is_cpython):
93-
u = 'u'
94-
abi = '%s%s%s%s%s' % (impl, interpreter_version(), d, m, u)
95-
elif soabi and soabi.startswith('cpython-'):
96-
abi = 'cp' + soabi.split('-')[1]
97-
elif soabi:
98-
abi = soabi.replace('.', '_').replace('-', '_')
99-
100-
return abi
101-
102-
103-
def _is_running_32bit():
104-
# type: () -> bool
105-
return sys.maxsize == 2147483647
106-
107-
108-
def get_platform():
109-
# type: () -> str
110-
"""Return our platform name 'win32', 'linux_x86_64'"""
111-
if sys.platform == 'darwin':
112-
# distutils.util.get_platform() returns the release based on the value
113-
# of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may
114-
# be significantly older than the user's current machine.
115-
release, _, machine = platform.mac_ver()
116-
split_ver = release.split('.')
117-
118-
if machine == "x86_64" and _is_running_32bit():
119-
machine = "i386"
120-
elif machine == "ppc64" and _is_running_32bit():
121-
machine = "ppc"
122-
123-
return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)
124-
125-
# XXX remove distutils dependency
126-
result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
127-
if result == "linux_x86_64" and _is_running_32bit():
128-
# 32 bit Python program (running on a 64 bit Linux): pip should only
129-
# install and run 32 bit compiled extensions in that case.
130-
result = "linux_i686"
131-
132-
return result
133-
134-
135-
def is_linux_armhf():
136-
# type: () -> bool
137-
if get_platform() != "linux_armv7l":
138-
return False
139-
# hard-float ABI can be detected from the ELF header of the running
140-
# process
141-
try:
142-
with open(sys.executable, 'rb') as f:
143-
elf_header_raw = f.read(40) # read 40 first bytes of ELF header
144-
except (IOError, OSError, TypeError):
145-
return False
146-
if elf_header_raw is None or len(elf_header_raw) < 40:
147-
return False
148-
if isinstance(elf_header_raw, str):
149-
elf_header = [ord(c) for c in elf_header_raw]
150-
else:
151-
elf_header = [b for b in elf_header_raw]
152-
result = elf_header[0:4] == [0x7f, 0x45, 0x4c, 0x46] # ELF magic number
153-
result &= elf_header[4:5] == [1] # 32-bit ELF
154-
result &= elf_header[5:6] == [1] # little-endian
155-
result &= elf_header[18:20] == [0x28, 0] # ARM machine
156-
result &= elf_header[39:40] == [5] # ARM EABIv5
157-
result &= (elf_header[37:38][0] & 4) == 4 # EF_ARM_ABI_FLOAT_HARD
158-
return result
159-
160-
161-
def is_manylinux1_compatible():
162-
# type: () -> bool
163-
# Only Linux, and only x86-64 / i686
164-
if get_platform() not in {"linux_x86_64", "linux_i686"}:
165-
return False
166-
167-
# Check for presence of _manylinux module
168-
try:
169-
import _manylinux
170-
return bool(_manylinux.manylinux1_compatible)
171-
except (ImportError, AttributeError):
172-
# Fall through to heuristic check below
173-
pass
174-
175-
# Check glibc version. CentOS 5 uses glibc 2.5.
176-
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)
177-
178-
179-
def is_manylinux2010_compatible():
180-
# type: () -> bool
181-
# Only Linux, and only x86-64 / i686
182-
if get_platform() not in {"linux_x86_64", "linux_i686"}:
183-
return False
184-
185-
# Check for presence of _manylinux module
186-
try:
187-
import _manylinux
188-
return bool(_manylinux.manylinux2010_compatible)
189-
except (ImportError, AttributeError):
190-
# Fall through to heuristic check below
191-
pass
192-
193-
# Check glibc version. CentOS 6 uses glibc 2.12.
194-
return pip._internal.utils.glibc.have_compatible_glibc(2, 12)
195-
196-
197-
def is_manylinux2014_compatible():
198-
# type: () -> bool
199-
# Only Linux, and only supported architectures
200-
platform = get_platform()
201-
if platform not in {"linux_x86_64", "linux_i686", "linux_aarch64",
202-
"linux_armv7l", "linux_ppc64", "linux_ppc64le",
203-
"linux_s390x"}:
204-
return False
205-
206-
# check for hard-float ABI in case we're running linux_armv7l not to
207-
# install hard-float ABI wheel in a soft-float ABI environment
208-
if platform == "linux_armv7l" and not is_linux_armhf():
209-
return False
210-
211-
# Check for presence of _manylinux module
212-
try:
213-
import _manylinux
214-
return bool(_manylinux.manylinux2014_compatible)
215-
except (ImportError, AttributeError):
216-
# Fall through to heuristic check below
217-
pass
218-
219-
# Check glibc version. CentOS 7 uses glibc 2.17.
220-
return pip._internal.utils.glibc.have_compatible_glibc(2, 17)
221-
222-
223-
def get_all_minor_versions_as_strings(version_info):
224-
# type: (Tuple[int, ...]) -> List[str]
225-
versions = []
226-
major = version_info[:-1]
227-
# Support all previous minor Python versions.
228-
for minor in range(version_info[-1], -1, -1):
229-
versions.append(''.join(map(str, major + (minor,))))
230-
return versions
231-
232-
23335
def _mac_platforms(arch):
23436
# type: (str) -> List[str]
23537
match = _osx_arch_pat.match(arch)
@@ -273,27 +75,35 @@ def _custom_manylinux_platforms(arch):
27375
return arches
27476

27577

276-
def _get_custom_platforms(arch, platform):
277-
# type: (str, Optional[str]) -> List[str]
78+
def _get_custom_platforms(arch):
79+
# type: (str) -> List[str]
27880
arch_prefix, arch_sep, arch_suffix = arch.partition('_')
27981
if arch.startswith('macosx'):
28082
arches = _mac_platforms(arch)
28183
elif arch_prefix in ['manylinux2014', 'manylinux2010']:
28284
arches = _custom_manylinux_platforms(arch)
283-
elif platform is None:
284-
arches = []
285-
if is_manylinux2014_compatible():
286-
arches.append('manylinux2014' + arch_sep + arch_suffix)
287-
if is_manylinux2010_compatible():
288-
arches.append('manylinux2010' + arch_sep + arch_suffix)
289-
if is_manylinux1_compatible():
290-
arches.append('manylinux1' + arch_sep + arch_suffix)
291-
arches.append(arch)
29285
else:
29386
arches = [arch]
29487
return arches
29588

29689

90+
def _get_python_version(version):
91+
# type: (str) -> PythonVersion
92+
if len(version) > 1:
93+
return int(version[0]), int(version[1:])
94+
else:
95+
return (int(version[0]),)
96+
97+
98+
def _get_custom_interpreter(implementation=None, version=None):
99+
# type: (Optional[str], Optional[str]) -> str
100+
if implementation is None:
101+
implementation = interpreter_name()
102+
if version is None:
103+
version = interpreter_version()
104+
return "{}{}".format(implementation, version)
105+
106+
297107
def get_supported(
298108
version=None, # type: Optional[str]
299109
platform=None, # type: Optional[str]
@@ -313,59 +123,45 @@ def get_supported(
313123
:param abi: specify the exact abi you want valid
314124
tags for, or None. If None, use the local interpreter abi.
315125
"""
316-
supported = []
317-
318-
# Versions must be given with respect to the preference
319-
if version is None:
320-
version_info = get_impl_version_info()
321-
versions = get_all_minor_versions_as_strings(version_info)
126+
supported = [] # type: List[Tag]
127+
128+
python_version = None # type: Optional[PythonVersion]
129+
if version is not None:
130+
python_version = _get_python_version(version)
131+
132+
interpreter = _get_custom_interpreter(impl, version)
133+
134+
abis = None # type: Optional[List[str]]
135+
if abi is not None:
136+
abis = [abi]
137+
138+
platforms = None # type: Optional[List[str]]
139+
if platform is not None:
140+
platforms = _get_custom_platforms(platform)
141+
142+
is_cpython = (impl or interpreter_name()) == "cp"
143+
if is_cpython:
144+
supported.extend(
145+
cpython_tags(
146+
python_version=python_version,
147+
abis=abis,
148+
platforms=platforms,
149+
)
150+
)
322151
else:
323-
versions = [version]
324-
current_version = versions[0]
325-
other_versions = versions[1:]
326-
327-
impl = impl or interpreter_name()
328-
329-
abis = [] # type: List[str]
330-
331-
abi = abi or get_abi_tag()
332-
if abi:
333-
abis[0:0] = [abi]
334-
335-
supports_abi3 = not PY2 and impl == "cp"
336-
337-
if supports_abi3:
338-
abis.append("abi3")
339-
340-
abis.append('none')
341-
342-
arches = _get_custom_platforms(platform or get_platform(), platform)
343-
344-
# Current version, current API (built specifically for our Python):
345-
for abi in abis:
346-
for arch in arches:
347-
supported.append(('%s%s' % (impl, current_version), abi, arch))
348-
349-
# abi3 modules compatible with older version of Python
350-
if supports_abi3:
351-
for version in other_versions:
352-
# abi3 was introduced in Python 3.2
353-
if version in {'31', '30'}:
354-
break
355-
for arch in arches:
356-
supported.append(("%s%s" % (impl, version), "abi3", arch))
357-
358-
# Has binaries, does not use the Python API:
359-
for arch in arches:
360-
supported.append(('py%s' % (current_version[0]), 'none', arch))
361-
362-
# No abi / arch, but requires our implementation:
363-
supported.append(('%s%s' % (impl, current_version), 'none', 'any'))
364-
365-
# No abi / arch, generic Python
366-
supported.append(('py%s' % (current_version,), 'none', 'any'))
367-
supported.append(('py%s' % (current_version[0]), 'none', 'any'))
368-
for version in other_versions:
369-
supported.append(('py%s' % (version,), 'none', 'any'))
152+
supported.extend(
153+
generic_tags(
154+
interpreter=interpreter,
155+
abis=abis,
156+
platforms=platforms,
157+
)
158+
)
159+
supported.extend(
160+
compatible_tags(
161+
python_version=python_version,
162+
interpreter=interpreter,
163+
platforms=platforms,
164+
)
165+
)
370166

371-
return [Tag(*parts) for parts in supported]
167+
return supported

0 commit comments

Comments
 (0)