Skip to content

Commit f3f396f

Browse files
authored
Merge pull request #7102 from mayeut/manylinux2014
Add manylinux2014 support
2 parents 209e571 + d3d3cca commit f3f396f

File tree

4 files changed

+136
-7
lines changed

4 files changed

+136
-7
lines changed

news/7102.feature

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Implement manylinux2014 platform tag support. manylinux2014 is the successor
2+
to manylinux2010. It allows carefully compiled binary wheels to be installed
3+
on compatible Linux platforms. The manylinux2014 platform tag definition can
4+
be found in `PEP599 <https://www.python.org/dev/peps/pep-0599/>`_.

src/pip/_internal/pep425tags.py

+64
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ def get_platform():
164164
return result
165165

166166

167+
def is_linux_armhf():
168+
# type: () -> bool
169+
if get_platform() != "linux_armv7l":
170+
return False
171+
# hard-float ABI can be detected from the ELF header of the running
172+
# process
173+
try:
174+
with open(sys.executable, 'rb') as f:
175+
elf_header_raw = f.read(40) # read 40 first bytes of ELF header
176+
except (IOError, OSError, TypeError):
177+
return False
178+
if elf_header_raw is None or len(elf_header_raw) < 40:
179+
return False
180+
if isinstance(elf_header_raw, str):
181+
elf_header = [ord(c) for c in elf_header_raw]
182+
else:
183+
elf_header = [b for b in elf_header_raw]
184+
result = elf_header[0:4] == [0x7f, 0x45, 0x4c, 0x46] # ELF magic number
185+
result &= elf_header[4:5] == [1] # 32-bit ELF
186+
result &= elf_header[5:6] == [1] # little-endian
187+
result &= elf_header[18:20] == [0x28, 0] # ARM machine
188+
result &= elf_header[39:40] == [5] # ARM EABIv5
189+
result &= (elf_header[37:38][0] & 4) == 4 # EF_ARM_ABI_FLOAT_HARD
190+
return result
191+
192+
167193
def is_manylinux1_compatible():
168194
# type: () -> bool
169195
# Only Linux, and only x86-64 / i686
@@ -200,6 +226,32 @@ def is_manylinux2010_compatible():
200226
return pip._internal.utils.glibc.have_compatible_glibc(2, 12)
201227

202228

229+
def is_manylinux2014_compatible():
230+
# type: () -> bool
231+
# Only Linux, and only supported architectures
232+
platform = get_platform()
233+
if platform not in {"linux_x86_64", "linux_i686", "linux_aarch64",
234+
"linux_armv7l", "linux_ppc64", "linux_ppc64le",
235+
"linux_s390x"}:
236+
return False
237+
238+
# check for hard-float ABI in case we're running linux_armv7l not to
239+
# install hard-float ABI wheel in a soft-float ABI environment
240+
if platform == "linux_armv7l" and not is_linux_armhf():
241+
return False
242+
243+
# Check for presence of _manylinux module
244+
try:
245+
import _manylinux
246+
return bool(_manylinux.manylinux2014_compatible)
247+
except (ImportError, AttributeError):
248+
# Fall through to heuristic check below
249+
pass
250+
251+
# Check glibc version. CentOS 7 uses glibc 2.17.
252+
return pip._internal.utils.glibc.have_compatible_glibc(2, 17)
253+
254+
203255
def get_darwin_arches(major, minor, machine):
204256
# type: (int, int, str) -> List[str]
205257
"""Return a list of supported arches (including group arches) for
@@ -333,6 +385,16 @@ def get_supported(
333385
else:
334386
# arch pattern didn't match (?!)
335387
arches = [arch]
388+
elif arch_prefix == 'manylinux2014':
389+
arches = [arch]
390+
# manylinux1/manylinux2010 wheels run on most manylinux2014 systems
391+
# with the exception of wheels depending on ncurses. PEP 599 states
392+
# manylinux1/manylinux2010 wheels should be considered
393+
# manylinux2014 wheels:
394+
# https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels
395+
if arch_suffix in {'i686', 'x86_64'}:
396+
arches.append('manylinux2010' + arch_sep + arch_suffix)
397+
arches.append('manylinux1' + arch_sep + arch_suffix)
336398
elif arch_prefix == 'manylinux2010':
337399
# manylinux1 wheels run on most manylinux2010 systems with the
338400
# exception of wheels depending on ncurses. PEP 571 states
@@ -341,6 +403,8 @@ def get_supported(
341403
arches = [arch, 'manylinux1' + arch_sep + arch_suffix]
342404
elif platform is None:
343405
arches = []
406+
if is_manylinux2014_compatible():
407+
arches.append('manylinux2014' + arch_sep + arch_suffix)
344408
if is_manylinux2010_compatible():
345409
arches.append('manylinux2010' + arch_sep + arch_suffix)
346410
if is_manylinux1_compatible():

tests/functional/test_download.py

+4
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ class TestDownloadPlatformManylinuxes(object):
331331
"linux_x86_64",
332332
"manylinux1_x86_64",
333333
"manylinux2010_x86_64",
334+
"manylinux2014_x86_64",
334335
])
335336
def test_download_universal(self, platform, script, data):
336337
"""
@@ -353,6 +354,9 @@ def test_download_universal(self, platform, script, data):
353354
("manylinux1_x86_64", "manylinux1_x86_64"),
354355
("manylinux1_x86_64", "manylinux2010_x86_64"),
355356
("manylinux2010_x86_64", "manylinux2010_x86_64"),
357+
("manylinux1_x86_64", "manylinux2014_x86_64"),
358+
("manylinux2010_x86_64", "manylinux2014_x86_64"),
359+
("manylinux2014_x86_64", "manylinux2014_x86_64"),
356360
])
357361
def test_download_compatible_manylinuxes(
358362
self, wheel_abi, platform, script, data,

tests/unit/test_pep425tags.py

+64-7
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def test_manual_abi_dm_flags(self):
137137
@pytest.mark.parametrize('is_manylinux_compatible', [
138138
pep425tags.is_manylinux1_compatible,
139139
pep425tags.is_manylinux2010_compatible,
140+
pep425tags.is_manylinux2014_compatible,
140141
])
141142
class TestManylinuxTags(object):
142143
"""
@@ -156,28 +157,28 @@ def test_manylinux_compatible_on_linux_x86_64(self,
156157
@patch('pip._internal.pep425tags.get_platform', lambda: 'linux_i686')
157158
@patch('pip._internal.utils.glibc.have_compatible_glibc',
158159
lambda major, minor: True)
159-
def test_manylinux1_compatible_on_linux_i686(self,
160-
is_manylinux_compatible):
160+
def test_manylinux_compatible_on_linux_i686(self,
161+
is_manylinux_compatible):
161162
"""
162-
Test that manylinux1 is enabled on linux_i686
163+
Test that manylinuxes are enabled on linux_i686
163164
"""
164165
assert is_manylinux_compatible()
165166

166167
@patch('pip._internal.pep425tags.get_platform', lambda: 'linux_x86_64')
167168
@patch('pip._internal.utils.glibc.have_compatible_glibc',
168169
lambda major, minor: False)
169-
def test_manylinux1_2(self, is_manylinux_compatible):
170+
def test_manylinux_2(self, is_manylinux_compatible):
170171
"""
171-
Test that manylinux1 is disabled with incompatible glibc
172+
Test that manylinuxes are disabled with incompatible glibc
172173
"""
173174
assert not is_manylinux_compatible()
174175

175176
@patch('pip._internal.pep425tags.get_platform', lambda: 'arm6vl')
176177
@patch('pip._internal.utils.glibc.have_compatible_glibc',
177178
lambda major, minor: True)
178-
def test_manylinux1_3(self, is_manylinux_compatible):
179+
def test_manylinux_3(self, is_manylinux_compatible):
179180
"""
180-
Test that manylinux1 is disabled on arm6vl
181+
Test that manylinuxes are disabled on arm6vl
181182
"""
182183
assert not is_manylinux_compatible()
183184

@@ -186,6 +187,8 @@ class TestManylinux1Tags(object):
186187

187188
@patch('pip._internal.pep425tags.is_manylinux2010_compatible',
188189
lambda: False)
190+
@patch('pip._internal.pep425tags.is_manylinux2014_compatible',
191+
lambda: False)
189192
@patch('pip._internal.pep425tags.get_platform', lambda: 'linux_x86_64')
190193
@patch('pip._internal.utils.glibc.have_compatible_glibc',
191194
lambda major, minor: True)
@@ -210,6 +213,8 @@ def test_manylinux1_tag_is_first(self):
210213

211214
class TestManylinux2010Tags(object):
212215

216+
@patch('pip._internal.pep425tags.is_manylinux2014_compatible',
217+
lambda: False)
213218
@patch('pip._internal.pep425tags.get_platform', lambda: 'linux_x86_64')
214219
@patch('pip._internal.utils.glibc.have_compatible_glibc',
215220
lambda major, minor: True)
@@ -253,3 +258,55 @@ def test_manylinux2010_implies_manylinux1(self, manylinux2010, manylinux1):
253258
if arches == ['any']:
254259
continue
255260
assert arches[:2] == [manylinux2010, manylinux1]
261+
262+
263+
class TestManylinux2014Tags(object):
264+
265+
@patch('pip._internal.pep425tags.get_platform', lambda: 'linux_x86_64')
266+
@patch('pip._internal.utils.glibc.have_compatible_glibc',
267+
lambda major, minor: True)
268+
@patch('sys.platform', 'linux2')
269+
def test_manylinux2014_tag_is_first(self):
270+
"""
271+
Test that the more specific tag manylinux2014 comes first.
272+
"""
273+
groups = {}
274+
for pyimpl, abi, arch in pep425tags.get_supported():
275+
groups.setdefault((pyimpl, abi), []).append(arch)
276+
277+
for arches in groups.values():
278+
if arches == ['any']:
279+
continue
280+
# Expect the most specific arch first:
281+
if len(arches) == 5:
282+
assert arches == ['manylinux2014_x86_64',
283+
'manylinux2010_x86_64',
284+
'manylinux1_x86_64',
285+
'linux_x86_64',
286+
'any']
287+
else:
288+
assert arches == ['manylinux2014_x86_64',
289+
'manylinux2010_x86_64',
290+
'manylinux1_x86_64',
291+
'linux_x86_64']
292+
293+
@pytest.mark.parametrize("manylinuxA,manylinuxB", [
294+
("manylinux2014_x86_64", ["manylinux2010_x86_64",
295+
"manylinux1_x86_64"]),
296+
("manylinux2014_i686", ["manylinux2010_i686", "manylinux1_i686"]),
297+
])
298+
def test_manylinuxA_implies_manylinuxB(self, manylinuxA, manylinuxB):
299+
"""
300+
Specifying manylinux2014 implies manylinux2010/manylinux1.
301+
"""
302+
groups = {}
303+
supported = pep425tags.get_supported(platform=manylinuxA)
304+
for pyimpl, abi, arch in supported:
305+
groups.setdefault((pyimpl, abi), []).append(arch)
306+
307+
expected_arches = [manylinuxA]
308+
expected_arches.extend(manylinuxB)
309+
for arches in groups.values():
310+
if arches == ['any']:
311+
continue
312+
assert arches[:3] == expected_arches

0 commit comments

Comments
 (0)