Skip to content

Commit e75015c

Browse files
committed
Support PEP 600 tags
1 parent d2e29ed commit e75015c

File tree

2 files changed

+156
-32
lines changed

2 files changed

+156
-32
lines changed

packaging/tags.py

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
1515
del imp
16+
import collections
1617
import logging
1718
import os
1819
import platform
@@ -57,6 +58,24 @@
5758
_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
5859

5960

61+
_LEGACY_MANYLINUX_MAP = {
62+
# CentOS 7 w/ glibc 2.17 (PEP 599)
63+
(2, 17): "manylinux2014",
64+
# CentOS 6 w/ glibc 2.12 (PEP 571)
65+
(2, 12): "manylinux2010",
66+
# CentOS 5 w/ glibc 2.5 (PEP 513)
67+
(2, 5): "manylinux1",
68+
}
69+
70+
# If glibc ever changes its major version, we need to know what the last
71+
# minor version was, so we can build the complete list of all versions.
72+
# For now, guess what the highest minor version might be, assume it will
73+
# be 50 for testing. Once this actually happens, update the dictionary
74+
# with the actual value.
75+
_LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int]
76+
glibcVersion = collections.namedtuple("Version", ["major", "minor"])
77+
78+
6079
class Tag(object):
6180
"""
6281
A representation of the tag triple for a wheel.
@@ -494,9 +513,16 @@ def _glibc_version_string_ctypes():
494513

495514

496515
# Separated out from have_compatible_glibc for easier unit testing.
497-
def _check_glibc_version(version_str, required_major, minimum_minor):
516+
def _check_glibc_version(version_str, tag_major, tag_minor):
498517
# type: (str, int, int) -> bool
499-
# Parse string and check against requested version.
518+
# Check against requested version.
519+
sys_major, sys_minor = _parse_glibc_version(version_str)
520+
return (sys_major, sys_minor) >= (tag_major, tag_minor)
521+
522+
523+
def _parse_glibc_version(version_str):
524+
# type: (str) -> Tuple[int, int]
525+
# Parse glibc version
500526
#
501527
# We use a regexp instead of str.split because we want to discard any
502528
# random junk that might come after the minor version -- this might happen
@@ -509,19 +535,24 @@ def _check_glibc_version(version_str, required_major, minimum_minor):
509535
" got: %s" % version_str,
510536
RuntimeWarning,
511537
)
512-
return False
513-
return (
514-
int(m.group("major")) == required_major
515-
and int(m.group("minor")) >= minimum_minor
516-
)
538+
return -1, -1
539+
return (int(m.group("major")), int(m.group("minor")))
517540

518541

519-
def _have_compatible_glibc(required_major, minimum_minor):
542+
def _have_compatible_glibc(tag_major, tag_minor):
520543
# type: (int, int) -> bool
521544
version_str = _glibc_version_string()
522545
if version_str is None:
523546
return False
524-
return _check_glibc_version(version_str, required_major, minimum_minor)
547+
return _check_glibc_version(version_str, tag_major, tag_minor)
548+
549+
550+
def _get_glibc_version():
551+
# type: () -> Tuple[int, int]
552+
version_str = _glibc_version_string()
553+
if version_str is None:
554+
return -1, -1
555+
return _parse_glibc_version(version_str)
525556

526557

527558
# Python does not provide platform information at sufficient granularity to
@@ -639,7 +670,49 @@ def _have_compatible_manylinux_abi(arch):
639670
return _is_linux_armhf()
640671
if arch == "i686":
641672
return _is_linux_i686()
642-
return True
673+
return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
674+
675+
676+
def _manylinux_tags(linux, arch):
677+
# type: (str, str) -> Iterator[str]
678+
# Oldest glibc to be supported is (2, 17).
679+
too_old_glibc2 = glibcVersion(2, 16)
680+
if arch in {"x86_64", "i686"}:
681+
# On x86/i686 also oldest glibc to be supported is (2, 5).
682+
too_old_glibc2 = glibcVersion(2, 4)
683+
current_glibc = glibcVersion(*_get_glibc_version())
684+
is_compat = False
685+
glibc_max_list = [current_glibc]
686+
# We can assume compatibility across glibc major versions.
687+
# https://sourceware.org/bugzilla/show_bug.cgi?id=24636
688+
#
689+
# Build a list of maximum glibc versions so that we can
690+
# output the canonical list of all glibc from current_glibc
691+
# down to too_old_glibc2, including all intermediary versions
692+
for glibc_major in range(current_glibc.major - 1, 1, -1):
693+
glibc_max_list.append(glibcVersion(glibc_major, _LAST_GLIBC_MINOR[glibc_major]))
694+
for glibc_max in glibc_max_list:
695+
if glibc_max.major == too_old_glibc2.major:
696+
min_minor = too_old_glibc2.minor
697+
else:
698+
# For other glibc major versions oldest supported is (x, 0).
699+
min_minor = -1
700+
for glibc_minor in range(glibc_max.minor, min_minor, -1):
701+
glibc_version = (glibc_max.major, glibc_minor)
702+
tag = "manylinux_{}_{}".format(*glibc_version)
703+
# Once _is_manylinux_compatible() is True, it is True for any
704+
# lower manylinux tag for this glibc major version.
705+
is_compat = is_compat or _is_manylinux_compatible(tag, glibc_version)
706+
if is_compat:
707+
yield linux.replace("linux", tag)
708+
# Handle the manylinux1, manylinux2010, manylinux2014 tags.
709+
if glibc_version in _LEGACY_MANYLINUX_MAP:
710+
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
711+
is_compat = is_compat or _is_manylinux_compatible(
712+
legacy_tag, glibc_version
713+
)
714+
if is_compat:
715+
yield linux.replace("linux", legacy_tag)
643716

644717

645718
def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
@@ -650,28 +723,10 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
650723
linux = "linux_i686"
651724
elif linux == "linux_aarch64":
652725
linux = "linux_armv7l"
653-
manylinux_support = []
654726
_, arch = linux.split("_", 1)
655727
if _have_compatible_manylinux_abi(arch):
656-
if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"}:
657-
manylinux_support.append(
658-
("manylinux2014", (2, 17))
659-
) # CentOS 7 w/ glibc 2.17 (PEP 599)
660-
if arch in {"x86_64", "i686"}:
661-
manylinux_support.append(
662-
("manylinux2010", (2, 12))
663-
) # CentOS 6 w/ glibc 2.12 (PEP 571)
664-
manylinux_support.append(
665-
("manylinux1", (2, 5))
666-
) # CentOS 5 w/ glibc 2.5 (PEP 513)
667-
manylinux_support_iter = iter(manylinux_support)
668-
for name, glibc_version in manylinux_support_iter:
669-
if _is_manylinux_compatible(name, glibc_version):
670-
yield linux.replace("linux", name)
671-
break
672-
# Support for a later manylinux implies support for an earlier version.
673-
for name, _ in manylinux_support_iter:
674-
yield linux.replace("linux", name)
728+
for tag in _manylinux_tags(linux, arch):
729+
yield tag
675730
yield linux
676731

677732

tests/test_tags.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ def test_is_manylinux_compatible_glibc_support(
326326
("2.4", 2, 4, True),
327327
("2.4", 2, 5, False),
328328
("2.4", 2, 3, True),
329-
("3.4", 2, 4, False),
329+
("3.4", 2, 4, True),
330330
],
331331
)
332332
def test_check_glibc_version(self, version_str, major, minor, expected):
@@ -373,6 +373,13 @@ def test_glibc_version_string_confstr(self, monkeypatch):
373373
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
374374
assert tags._glibc_version_string_confstr() == "2.20"
375375

376+
def test_glibc_version_string_fail(self, monkeypatch):
377+
monkeypatch.setattr(os, "confstr", lambda x: None, raising=False)
378+
monkeypatch.setitem(sys.modules, "ctypes", None)
379+
assert tags._glibc_version_string() is None
380+
assert tags._have_compatible_glibc(2, 5) is False
381+
assert tags._get_glibc_version() == (-1, -1)
382+
376383
@pytest.mark.parametrize(
377384
"failure",
378385
[pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"],
@@ -433,12 +440,14 @@ def test_linux_platforms_32_64bit_on_64bit_os(
433440
self, arch, is_32bit, expected, monkeypatch
434441
):
435442
monkeypatch.setattr(distutils.util, "get_platform", lambda: arch)
443+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
436444
monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
437445
linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[-1]
438446
assert linux_platform == expected
439447

440448
def test_linux_platforms_manylinux_unsupported(self, monkeypatch):
441449
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
450+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
442451
monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False)
443452
linux_platform = list(tags._linux_platforms(is_32bit=False))
444453
assert linux_platform == ["linux_x86_64"]
@@ -450,6 +459,7 @@ def test_linux_platforms_manylinux1(self, is_x86, monkeypatch):
450459
if platform.system() != "Linux" or not is_x86:
451460
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
452461
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
462+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
453463
platforms = list(tags._linux_platforms(is_32bit=False))
454464
arch = platform.machine()
455465
assert platforms == ["manylinux1_" + arch, "linux_" + arch]
@@ -461,9 +471,21 @@ def test_linux_platforms_manylinux2010(self, is_x86, monkeypatch):
461471
if platform.system() != "Linux" or not is_x86:
462472
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
463473
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
474+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
464475
platforms = list(tags._linux_platforms(is_32bit=False))
465476
arch = platform.machine()
466-
expected = ["manylinux2010_" + arch, "manylinux1_" + arch, "linux_" + arch]
477+
expected = [
478+
"manylinux2010_" + arch,
479+
"manylinux_2_11_" + arch,
480+
"manylinux_2_10_" + arch,
481+
"manylinux_2_9_" + arch,
482+
"manylinux_2_8_" + arch,
483+
"manylinux_2_7_" + arch,
484+
"manylinux_2_6_" + arch,
485+
"manylinux_2_5_" + arch,
486+
"manylinux1_" + arch,
487+
"linux_" + arch,
488+
]
467489
assert platforms == expected
468490

469491
def test_linux_platforms_manylinux2014(self, is_x86, monkeypatch):
@@ -473,17 +495,31 @@ def test_linux_platforms_manylinux2014(self, is_x86, monkeypatch):
473495
if platform.system() != "Linux" or not is_x86:
474496
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64")
475497
monkeypatch.setattr(platform, "machine", lambda: "x86_64")
498+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
476499
platforms = list(tags._linux_platforms(is_32bit=False))
477500
arch = platform.machine()
478501
expected = [
479502
"manylinux2014_" + arch,
503+
"manylinux_2_16_" + arch,
504+
"manylinux_2_15_" + arch,
505+
"manylinux_2_14_" + arch,
506+
"manylinux_2_13_" + arch,
507+
"manylinux_2_12_" + arch,
480508
"manylinux2010_" + arch,
509+
"manylinux_2_11_" + arch,
510+
"manylinux_2_10_" + arch,
511+
"manylinux_2_9_" + arch,
512+
"manylinux_2_8_" + arch,
513+
"manylinux_2_7_" + arch,
514+
"manylinux_2_6_" + arch,
515+
"manylinux_2_5_" + arch,
481516
"manylinux1_" + arch,
482517
"linux_" + arch,
483518
]
484519
assert platforms == expected
485520

486521
def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch):
522+
monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.30")
487523
monkeypatch.setattr(
488524
tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
489525
)
@@ -498,6 +534,7 @@ def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch):
498534
assert platforms == expected
499535

500536
def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch):
537+
monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.20")
501538
monkeypatch.setattr(
502539
tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
503540
)
@@ -509,18 +546,50 @@ def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch):
509546
)
510547
platforms = list(tags._linux_platforms(is_32bit=True))
511548
expected = [
549+
# "manylinux_2_17_i686", # rejected since it comes before manylinux2014
512550
"manylinux2014_i686",
551+
"manylinux_2_16_i686",
552+
"manylinux_2_15_i686",
553+
"manylinux_2_14_i686",
554+
"manylinux_2_13_i686",
555+
"manylinux_2_12_i686",
513556
"manylinux2010_i686",
557+
"manylinux_2_11_i686",
558+
"manylinux_2_10_i686",
559+
"manylinux_2_9_i686",
560+
"manylinux_2_8_i686",
561+
"manylinux_2_7_i686",
562+
"manylinux_2_6_i686",
563+
"manylinux_2_5_i686",
514564
"manylinux1_i686",
515565
"linux_i686",
516566
]
517567
assert platforms == expected
518568

569+
def test_linux_platforms_manylinux_glibc3(self, monkeypatch):
570+
# test for a future glic 3.x version
571+
monkeypatch.setattr(tags, "_glibc_version_string", lambda: "3.2")
572+
monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: True)
573+
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_aarch64")
574+
monkeypatch.setattr(
575+
sys,
576+
"executable",
577+
os.path.join(os.path.dirname(__file__), "hello-world-aarch64"),
578+
)
579+
platforms = list(tags._linux_platforms())
580+
expected = (
581+
["manylinux_3_2_aarch64", "manylinux_3_1_aarch64", "manylinux_3_0_aarch64"]
582+
+ ["manylinux_2_{}_aarch64".format(i) for i in range(50, 16, -1)]
583+
+ ["manylinux2014_aarch64", "linux_aarch64"]
584+
)
585+
assert platforms == expected
586+
519587
def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch):
520588
monkeypatch.setattr(
521589
tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014"
522590
)
523591
monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l")
592+
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
524593
platforms = list(tags._linux_platforms(is_32bit=True))
525594
expected = ["linux_armv6l"]
526595
assert platforms == expected

0 commit comments

Comments
 (0)