From d83957b6e3f09bb3406e1bfe00171998f3c3259e Mon Sep 17 00:00:00 2001 From: "Robert T. McGibbon" Date: Tue, 23 Feb 2016 09:11:29 +0100 Subject: [PATCH] Implement PEP 513: manylinux1 platform tags --- pip/pep425tags.py | 55 +++++++++++++++++++++++++++++++++-- tests/unit/test_pep425tags.py | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/pip/pep425tags.py b/pip/pep425tags.py index 2d91ccc6d5f..740ac3927ea 100644 --- a/pip/pep425tags.py +++ b/pip/pep425tags.py @@ -6,6 +6,7 @@ import warnings import platform import logging +import ctypes try: import sysconfig @@ -124,7 +125,54 @@ def get_platform(): split_ver = release.split('.') return 'macosx_{0}_{1}_{2}'.format(split_ver[0], split_ver[1], machine) # XXX remove distutils dependency - return distutils.util.get_platform().replace('.', '_').replace('-', '_') + result = distutils.util.get_platform().replace('.', '_').replace('-', '_') + if result == "linux_x86_64" and sys.maxsize == 2147483647: + # 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_manylinux1_compatible(): + # 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 have_compatible_glibc(2, 5) + + +def have_compatible_glibc(major, minimum_minor): + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return False + + # Call gnu_get_libc_version, which returns a string like "2.5". + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + # Parse string and check against requested version. + version = [int(piece) for piece in version_str.split(".")] + if len(version) < 2: + warnings.warn("Expected glibc version with 2 components major.minor," + " got: %s" % version_str, RuntimeWarning) + return False + return version[0] == major and version[1] >= minimum_minor def get_supported(versions=None, noarch=False): @@ -189,6 +237,8 @@ def get_supported(versions=None, noarch=False): else: # arch pattern didn't match (?!) arches = [arch] + elif is_manylinux1_compatible(): + arches = [arch.replace('linux', 'manylinux1'), arch] else: arches = [arch] @@ -198,7 +248,8 @@ def get_supported(versions=None, noarch=False): supported.append(('%s%s' % (impl, versions[0]), abi, arch)) # Has binaries, does not use the Python API: - supported.append(('py%s' % (versions[0][0]), 'none', arch)) + for arch in arches: + supported.append(('py%s' % (versions[0][0]), 'none', arch)) # No abi / arch, but requires our implementation: for i, version in enumerate(versions): diff --git a/tests/unit/test_pep425tags.py b/tests/unit/test_pep425tags.py index 9f6d60990ea..6edee7d8852 100644 --- a/tests/unit/test_pep425tags.py +++ b/tests/unit/test_pep425tags.py @@ -104,3 +104,58 @@ def test_manual_abi_dm_flags(self): Test that the `dm` flags are set on a PyDebug, Pymalloc ABI tag. """ self.abi_tag_unicode('dm', {'Py_DEBUG': True, 'WITH_PYMALLOC': True}) + + +class TestManylinux1Tags(object): + + @patch('pip.pep425tags.get_platform', lambda: 'linux_x86_64') + @patch('pip.pep425tags.have_compatible_glibc', lambda major, minor: True) + def test_manylinux1_compatible_on_linux_x86_64(self): + """ + Test that manylinux1 is enabled on linux_x86_64 + """ + assert pep425tags.is_manylinux1_compatible() + + @patch('pip.pep425tags.get_platform', lambda: 'linux_i686') + @patch('pip.pep425tags.have_compatible_glibc', lambda major, minor: True) + def test_manylinux1_compatible_on_linux_i686(self): + """ + Test that manylinux1 is enabled on linux_i686 + """ + assert pep425tags.is_manylinux1_compatible() + + @patch('pip.pep425tags.get_platform', lambda: 'linux_x86_64') + @patch('pip.pep425tags.have_compatible_glibc', lambda major, minor: False) + def test_manylinux1_2(self): + """ + Test that manylinux1 is disabled with incompatible glibc + """ + assert not pep425tags.is_manylinux1_compatible() + + @patch('pip.pep425tags.get_platform', lambda: 'arm6vl') + @patch('pip.pep425tags.have_compatible_glibc', lambda major, minor: True) + def test_manylinux1_3(self): + """ + Test that manylinux1 is disabled on arm6vl + """ + assert not pep425tags.is_manylinux1_compatible() + + @patch('pip.pep425tags.get_platform', lambda: 'linux_x86_64') + @patch('pip.pep425tags.have_compatible_glibc', lambda major, minor: True) + @patch('sys.platform', 'linux2') + def test_manylinux1_tag_is_first(self): + """ + Test that the more specific tag manylinux1 comes first. + """ + groups = {} + for pyimpl, abi, arch in pep425tags.get_supported(): + groups.setdefault((pyimpl, abi), []).append(arch) + + for arches in groups.values(): + if arches == ['any']: + continue + # Expect the most specific arch first: + if len(arches) == 3: + assert arches == ['manylinux1_x86_64', 'linux_x86_64', 'any'] + else: + assert arches == ['manylinux1_x86_64', 'linux_x86_64']