diff --git a/packaging/tags.py b/packaging/tags.py index e35bbdad..e2bc6c61 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -13,6 +13,7 @@ EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] del imp +import collections import logging import os import platform @@ -57,6 +58,24 @@ _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int] +glibcVersion = collections.namedtuple("Version", ["major", "minor"]) + + class Tag(object): """ A representation of the tag triple for a wheel. @@ -416,19 +435,35 @@ def mac_platforms(version=None, arch=None): ) -# From PEP 513. -def _is_manylinux_compatible(name, glibc_version): - # type: (str, GlibcVersion) -> bool +# From PEP 513, PEP 600 +def _is_manylinux_compatible(name, arch, glibc_version): + # type: (str, str, GlibcVersion) -> bool + sys_glibc = _get_glibc_version() + if sys_glibc < glibc_version: + return False # Check for presence of _manylinux module. try: import _manylinux # noqa - - return bool(getattr(_manylinux, name + "_compatible")) - except (ImportError, AttributeError): - # Fall through to heuristic check below. + except ImportError: pass - - return _have_compatible_glibc(*glibc_version) + else: + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible( + glibc_version[0], glibc_version[1], arch + ) + if result is not None: + return bool(result) + else: + if glibc_version == (2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if glibc_version == (2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if glibc_version == (2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True def _glibc_version_string(): @@ -505,10 +540,9 @@ def _glibc_version_string_ctypes(): return version_str -# Separated out from have_compatible_glibc for easier unit testing. -def _check_glibc_version(version_str, required_major, minimum_minor): - # type: (str, int, int) -> bool - # Parse string and check against requested version. +def _parse_glibc_version(version_str): + # type: (str) -> Tuple[int, int] + # Parse glibc version. # # We use a regexp instead of str.split because we want to discard any # random junk that might come after the minor version -- this might happen @@ -521,19 +555,23 @@ def _check_glibc_version(version_str, required_major, minimum_minor): " got: %s" % version_str, RuntimeWarning, ) - return False - return ( - int(m.group("major")) == required_major - and int(m.group("minor")) >= minimum_minor - ) + return -1, -1 + return (int(m.group("major")), int(m.group("minor"))) + +_glibc_version = [] # type: List[Tuple[int, int]] -def _have_compatible_glibc(required_major, minimum_minor): - # type: (int, int) -> bool + +def _get_glibc_version(): + # type: () -> Tuple[int, int] + if _glibc_version: + return _glibc_version[0] version_str = _glibc_version_string() if version_str is None: - return False - return _check_glibc_version(version_str, required_major, minimum_minor) + _glibc_version.append((-1, -1)) + else: + _glibc_version.append(_parse_glibc_version(version_str)) + return _glibc_version[0] # Python does not provide platform information at sufficient granularity to @@ -651,7 +689,42 @@ def _have_compatible_manylinux_abi(arch): return _is_linux_armhf() if arch == "i686": return _is_linux_i686() - return True + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +def _manylinux_tags(linux, arch): + # type: (str, str) -> Iterator[str] + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = glibcVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = glibcVersion(2, 4) + current_glibc = glibcVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_max_list.append(glibcVersion(glibc_major, _LAST_GLIBC_MINOR[glibc_major])) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = (glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_manylinux_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_manylinux_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): @@ -662,28 +735,10 @@ def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): linux = "linux_i686" elif linux == "linux_aarch64": linux = "linux_armv7l" - manylinux_support = [] _, arch = linux.split("_", 1) if _have_compatible_manylinux_abi(arch): - if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"}: - manylinux_support.append( - ("manylinux2014", (2, 17)) - ) # CentOS 7 w/ glibc 2.17 (PEP 599) - if arch in {"x86_64", "i686"}: - manylinux_support.append( - ("manylinux2010", (2, 12)) - ) # CentOS 6 w/ glibc 2.12 (PEP 571) - manylinux_support.append( - ("manylinux1", (2, 5)) - ) # CentOS 5 w/ glibc 2.5 (PEP 513) - manylinux_support_iter = iter(manylinux_support) - for name, glibc_version in manylinux_support_iter: - if _is_manylinux_compatible(name, glibc_version): - yield linux.replace("linux", name) - break - # Support for a later manylinux implies support for an earlier version. - for name, _ in manylinux_support_iter: - yield linux.replace("linux", name) + for tag in _manylinux_tags(linux, arch): + yield tag yield linux diff --git a/tests/test_tags.py b/tests/test_tags.py index 0e445f2b..dbffa9b4 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -39,7 +39,7 @@ def is_x86(): @pytest.fixture def manylinux_module(monkeypatch): - monkeypatch.setattr(tags, "_have_compatible_glibc", lambda *args: False) + monkeypatch.setattr(tags, "_get_glibc_version", lambda *args: (2, 20)) module_name = "_manylinux" module = types.ModuleType(module_name) monkeypatch.setitem(sys.modules, module_name, module) @@ -285,26 +285,31 @@ def test_mac_platforms(self): class TestManylinuxPlatform: - def test_module_declaration_true(self, manylinux_module): - manylinux_module.manylinux1_compatible = True - assert tags._is_manylinux_compatible("manylinux1", (2, 5)) - - def test_module_declaration_false(self, manylinux_module): - manylinux_module.manylinux1_compatible = False - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) - - def test_module_declaration_missing_attribute(self, manylinux_module): - try: - del manylinux_module.manylinux1_compatible - except AttributeError: - pass - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + def teardown_method(self): + # Clear the version cache + tags._glibc_version = [] - def test_is_manylinux_compatible_module_support( - self, manylinux_module, monkeypatch + @pytest.mark.parametrize("tf", (True, False)) + @pytest.mark.parametrize( + "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))) + ) + def test_module_declaration( + self, monkeypatch, manylinux_module, attribute, glibc, tf + ): + manylinux = "manylinux{}_compatible".format(attribute) + monkeypatch.setattr(manylinux_module, manylinux, tf, raising=False) + res = tags._is_manylinux_compatible(manylinux, "x86_64", glibc) + assert tf is res + + @pytest.mark.parametrize( + "attribute,glibc", (("1", (2, 5)), ("2010", (2, 12)), ("2014", (2, 17))) + ) + def test_module_declaration_missing_attribute( + self, monkeypatch, manylinux_module, attribute, glibc ): - monkeypatch.setitem(sys.modules, manylinux_module.__name__, None) - assert not tags._is_manylinux_compatible("manylinux1", (2, 5)) + manylinux = "manylinux{}_compatible".format(attribute) + monkeypatch.delattr(manylinux_module, manylinux, raising=False) + assert tags._is_manylinux_compatible(manylinux, "x86_64", glibc) @pytest.mark.parametrize( "version,compatible", (((2, 0), True), ((2, 5), True), ((2, 10), False)) @@ -313,29 +318,16 @@ def test_is_manylinux_compatible_glibc_support( self, version, compatible, monkeypatch ): monkeypatch.setitem(sys.modules, "_manylinux", None) - monkeypatch.setattr( - tags, - "_have_compatible_glibc", - lambda major, minor: (major, minor) <= (2, 5), + monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 5)) + assert ( + bool(tags._is_manylinux_compatible("manylinux1", "any", version)) + == compatible ) - assert bool(tags._is_manylinux_compatible("manylinux1", version)) == compatible - - @pytest.mark.parametrize( - "version_str,major,minor,expected", - [ - ("2.4", 2, 4, True), - ("2.4", 2, 5, False), - ("2.4", 2, 3, True), - ("3.4", 2, 4, False), - ], - ) - def test_check_glibc_version(self, version_str, major, minor, expected): - assert expected == tags._check_glibc_version(version_str, major, minor) @pytest.mark.parametrize("version_str", ["glibc-2.4.5", "2"]) def test_check_glibc_version_warning(self, version_str): with warnings.catch_warnings(record=True) as w: - tags._check_glibc_version(version_str, 2, 4) + tags._parse_glibc_version(version_str) assert len(w) == 1 assert issubclass(w[0].category, RuntimeWarning) @@ -373,6 +365,12 @@ def test_glibc_version_string_confstr(self, monkeypatch): monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) assert tags._glibc_version_string_confstr() == "2.20" + def test_glibc_version_string_fail(self, monkeypatch): + monkeypatch.setattr(os, "confstr", lambda x: None, raising=False) + monkeypatch.setitem(sys.modules, "ctypes", None) + assert tags._glibc_version_string() is None + assert tags._get_glibc_version() == (-1, -1) + @pytest.mark.parametrize( "failure", [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"], @@ -414,18 +412,18 @@ def test_get_config_var_does_log(self, monkeypatch): ] @pytest.mark.skipif(platform.system() != "Linux", reason="requires Linux") - def test_have_compatible_glibc_linux(self): + def test_is_manylinux_compatible_old(self): # Assuming no one is running this test with a version of glibc released in # 1997. - assert tags._have_compatible_glibc(2, 0) + assert tags._is_manylinux_compatible("any", "any", (2, 0)) - def test_have_compatible_glibc(self, monkeypatch): + def test_is_manylinux_compatible(self, monkeypatch): monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.4") - assert tags._have_compatible_glibc(2, 4) + assert tags._is_manylinux_compatible("", "any", (2, 4)) def test_glibc_version_string_none(self, monkeypatch): monkeypatch.setattr(tags, "_glibc_version_string", lambda: None) - assert not tags._have_compatible_glibc(2, 4) + assert not tags._is_manylinux_compatible("any", "any", (2, 4)) @pytest.mark.parametrize( "arch,is_32bit,expected", @@ -440,59 +438,83 @@ def test_linux_platforms_32_64bit_on_64bit_os( self, arch, is_32bit, expected, monkeypatch ): monkeypatch.setattr(distutils.util, "get_platform", lambda: arch) + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) linux_platform = list(tags._linux_platforms(is_32bit=is_32bit))[-1] assert linux_platform == expected def test_linux_platforms_manylinux_unsupported(self, monkeypatch): monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda *args: False) linux_platform = list(tags._linux_platforms(is_32bit=False)) assert linux_platform == ["linux_x86_64"] def test_linux_platforms_manylinux1(self, is_x86, monkeypatch): monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux1" + tags, "_is_manylinux_compatible", lambda name, *args: name == "manylinux1" ) - if platform.system() != "Linux" or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) platforms = list(tags._linux_platforms(is_32bit=False)) arch = platform.machine() assert platforms == ["manylinux1_" + arch, "linux_" + arch] def test_linux_platforms_manylinux2010(self, is_x86, monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2010" - ) - if platform.system() != "Linux" or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.12", raising=False) platforms = list(tags._linux_platforms(is_32bit=False)) arch = platform.machine() - expected = ["manylinux2010_" + arch, "manylinux1_" + arch, "linux_" + arch] + expected = [ + "manylinux_2_12_" + arch, + "manylinux2010_" + arch, + "manylinux_2_11_" + arch, + "manylinux_2_10_" + arch, + "manylinux_2_9_" + arch, + "manylinux_2_8_" + arch, + "manylinux_2_7_" + arch, + "manylinux_2_6_" + arch, + "manylinux_2_5_" + arch, + "manylinux1_" + arch, + "linux_" + arch, + ] assert platforms == expected def test_linux_platforms_manylinux2014(self, is_x86, monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" - ) - if platform.system() != "Linux" or not is_x86: - monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") - monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.17", raising=False) platforms = list(tags._linux_platforms(is_32bit=False)) arch = platform.machine() expected = [ + "manylinux_2_17_" + arch, "manylinux2014_" + arch, + "manylinux_2_16_" + arch, + "manylinux_2_15_" + arch, + "manylinux_2_14_" + arch, + "manylinux_2_13_" + arch, + "manylinux_2_12_" + arch, "manylinux2010_" + arch, + "manylinux_2_11_" + arch, + "manylinux_2_10_" + arch, + "manylinux_2_9_" + arch, + "manylinux_2_8_" + arch, + "manylinux_2_7_" + arch, + "manylinux_2_6_" + arch, + "manylinux_2_5_" + arch, "manylinux1_" + arch, "linux_" + arch, ] assert platforms == expected def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch): + monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.30") monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" + tags, + "_is_manylinux_compatible", + lambda name, *args: name == "manylinux2014", ) monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv7l") monkeypatch.setattr( @@ -505,9 +527,7 @@ def test_linux_platforms_manylinux2014_armhf_abi(self, monkeypatch): assert platforms == expected def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch): - monkeypatch.setattr( - tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" - ) + monkeypatch.setattr(tags, "_glibc_version_string", lambda: "2.17") monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") monkeypatch.setattr( sys, @@ -516,18 +536,50 @@ def test_linux_platforms_manylinux2014_i386_abi(self, monkeypatch): ) platforms = list(tags._linux_platforms(is_32bit=True)) expected = [ + "manylinux_2_17_i686", "manylinux2014_i686", + "manylinux_2_16_i686", + "manylinux_2_15_i686", + "manylinux_2_14_i686", + "manylinux_2_13_i686", + "manylinux_2_12_i686", "manylinux2010_i686", + "manylinux_2_11_i686", + "manylinux_2_10_i686", + "manylinux_2_9_i686", + "manylinux_2_8_i686", + "manylinux_2_7_i686", + "manylinux_2_6_i686", + "manylinux_2_5_i686", "manylinux1_i686", "linux_i686", ] assert platforms == expected + def test_linux_platforms_manylinux_glibc3(self, monkeypatch): + # test for a future glic 3.x version + monkeypatch.setattr(tags, "_glibc_version_string", lambda: "3.2") + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, *args: True) + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_aarch64") + monkeypatch.setattr( + sys, + "executable", + os.path.join(os.path.dirname(__file__), "hello-world-aarch64"), + ) + platforms = list(tags._linux_platforms()) + expected = ( + ["manylinux_3_2_aarch64", "manylinux_3_1_aarch64", "manylinux_3_0_aarch64"] + + ["manylinux_2_{}_aarch64".format(i) for i in range(50, 16, -1)] + + ["manylinux2014_aarch64", "linux_aarch64"] + ) + assert platforms == expected + def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch): monkeypatch.setattr( tags, "_is_manylinux_compatible", lambda name, _: name == "manylinux2014" ) monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) platforms = list(tags._linux_platforms(is_32bit=True)) expected = ["linux_armv6l"] assert platforms == expected @@ -539,7 +591,7 @@ def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch): def test_linux_platforms_not_manylinux_abi( self, monkeypatch, machine, abi, alt_machine ): - monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: True) + monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: False) monkeypatch.setattr( distutils.util, "get_platform", lambda: "linux_{}".format(machine) ) @@ -1084,6 +1136,10 @@ def test_default_platforms(self, monkeypatch): class TestSysTags: + def teardown_method(self): + # Clear the version cache + tags._glibc_version = [] + @pytest.mark.parametrize( "name,expected", [("CPython", "cp"), ("PyPy", "pp"), ("Jython", "jy"), ("IronPython", "ip")], @@ -1156,3 +1212,103 @@ def test_generic(self, monkeypatch): "py" + tags._version_nodot((sys.version_info[0], 0)), "none", "any" ) assert result[-1] == expected + + def test_linux_platforms_manylinux2014_armv6l(self, monkeypatch, manylinux_module): + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_armv6l") + monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False) + platforms = list(tags._linux_platforms(is_32bit=True)) + expected = ["linux_armv6l"] + assert platforms == expected + + def test_skip_manylinux_2014(self, monkeypatch, manylinux_module): + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_ppc64") + monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 20)) + monkeypatch.setattr( + manylinux_module, "manylinux2014_compatible", False, raising=False + ) + expected = [ + "manylinux_2_20_ppc64", + "manylinux_2_19_ppc64", + "manylinux_2_18_ppc64", + # "manylinux2014_ppc64", # this one is skipped + # "manylinux_2_17_ppc64", # this one is also skipped + "linux_ppc64", + ] + platforms = list(tags._linux_platforms()) + assert platforms == expected + + @pytest.mark.parametrize( + "machine, abi, alt_machine", + [("x86_64", "x32", "i686"), ("armv7l", "armel", "armv7l")], + ) + def test_linux_platforms_not_manylinux_abi( + self, monkeypatch, manylinux_module, machine, abi, alt_machine + ): + monkeypatch.setattr( + distutils.util, "get_platform", lambda: "linux_{}".format(machine) + ) + monkeypatch.setattr( + sys, + "executable", + os.path.join( + os.path.dirname(__file__), "hello-world-{}-{}".format(machine, abi) + ), + ) + platforms = list(tags._linux_platforms(is_32bit=True)) + expected = ["linux_{}".format(alt_machine)] + assert platforms == expected + + @pytest.mark.parametrize( + "machine, major, minor, tf", [("x86_64", 2, 20, False), ("s390x", 2, 22, True)] + ) + def test_linux_use_manylinux_compatible( + self, monkeypatch, manylinux_module, machine, major, minor, tf + ): + def manylinux_compatible(tag_major, tag_minor, tag_arch): + if tag_major == 2 and tag_minor == 22: + return tag_arch == "s390x" + return False + + monkeypatch.setattr(tags, "_get_glibc_version", lambda: (major, minor)) + monkeypatch.setattr( + distutils.util, "get_platform", lambda: "linux_{}".format(machine) + ) + monkeypatch.setattr( + manylinux_module, + "manylinux_compatible", + manylinux_compatible, + raising=False, + ) + platforms = list(tags._linux_platforms()) + if tf: + expected = ["manylinux_2_22_{}".format(machine)] + else: + expected = [] + expected.append("linux_{}".format(machine)) + assert platforms == expected + + def test_linux_use_manylinux_compatible_none(self, monkeypatch, manylinux_module): + def manylinux_compatible(tag_major, tag_minor, tag_arch): + if tag_major == 2 and tag_minor < 25: + return False + return None + + monkeypatch.setattr(tags, "_get_glibc_version", lambda: (2, 30)) + monkeypatch.setattr(distutils.util, "get_platform", lambda: "linux_x86_64") + monkeypatch.setattr( + manylinux_module, + "manylinux_compatible", + manylinux_compatible, + raising=False, + ) + platforms = list(tags._linux_platforms()) + expected = [ + "manylinux_2_30_x86_64", + "manylinux_2_29_x86_64", + "manylinux_2_28_x86_64", + "manylinux_2_27_x86_64", + "manylinux_2_26_x86_64", + "manylinux_2_25_x86_64", + "linux_x86_64", + ] + assert platforms == expected