Skip to content

Commit

Permalink
Reduce dependency on ctypes when discovering glibc version. (pypa#6678)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandtbucher authored and pradyunsg committed Jul 21, 2019
1 parent e308497 commit 8582f7e
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
1 change: 1 addition & 0 deletions news/6543.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prefer ``os.confstr`` to ``ctypes`` when extracting glibc version info.
1 change: 1 addition & 0 deletions news/6675.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prefer ``os.confstr`` to ``ctypes`` when extracting glibc version info.
31 changes: 29 additions & 2 deletions src/pip/_internal/utils/glibc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import absolute_import

import ctypes
import os
import re
import warnings

Expand All @@ -13,6 +13,33 @@
def glibc_version_string():
# type: () -> Optional[str]
"Returns glibc version string, or None if not using glibc."
return glibc_version_string_confstr() or glibc_version_string_ctypes()


def glibc_version_string_confstr():
# type: () -> Optional[str]
"Primary implementation of glibc_version_string using os.confstr."
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
# to be broken or missing. This strategy is used in the standard library
# platform module:
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
try:
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
except (AttributeError, OSError, ValueError):
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
return None
return version


def glibc_version_string_ctypes():
# type: () -> Optional[str]
"Fallback implementation of glibc_version_string using ctypes."

try:
import ctypes
except ImportError:
return None

# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
# manpage says, "If filename is NULL, then the returned handle is for the
Expand Down Expand Up @@ -56,7 +83,7 @@ def check_glibc_version(version_str, required_major, minimum_minor):

def have_compatible_glibc(required_major, minimum_minor):
# type: (int, int) -> bool
version_str = glibc_version_string() # type: Optional[str]
version_str = glibc_version_string()
if version_str is None:
return False
return check_glibc_version(version_str, required_major, minimum_minor)
Expand Down
38 changes: 37 additions & 1 deletion tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
)
from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated
from pip._internal.utils.encoding import BOMS, auto_decode
from pip._internal.utils.glibc import check_glibc_version
from pip._internal.utils.glibc import (
check_glibc_version, glibc_version_string, glibc_version_string_confstr,
glibc_version_string_ctypes,
)
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.misc import (
call_subprocess, egg_link_path, ensure_dir, format_command_args,
Expand Down Expand Up @@ -704,6 +707,10 @@ def raising_mkdir(*args, **kwargs):
pass


def raises(error):
raise error


class TestGlibc(object):
def test_manylinux_check_glibc_version(self):
"""
Expand Down Expand Up @@ -737,6 +744,35 @@ def test_manylinux_check_glibc_version(self):
# Didn't find the warning we were expecting
assert False

def test_glibc_version_string(self, monkeypatch):
monkeypatch.setattr(
os, "confstr", lambda x: "glibc 2.20", raising=False,
)
assert glibc_version_string() == "2.20"

def test_glibc_version_string_confstr(self, monkeypatch):
monkeypatch.setattr(
os, "confstr", lambda x: "glibc 2.20", raising=False,
)
assert glibc_version_string_confstr() == "2.20"

@pytest.mark.parametrize("failure", [
lambda x: raises(ValueError),
lambda x: raises(OSError),
lambda x: "XXX",
])
def test_glibc_version_string_confstr_fail(self, monkeypatch, failure):
monkeypatch.setattr(os, "confstr", failure, raising=False)
assert glibc_version_string_confstr() is None

def test_glibc_version_string_confstr_missing(self, monkeypatch):
monkeypatch.delattr(os, "confstr", raising=False)
assert glibc_version_string_confstr() is None

def test_glibc_version_string_ctypes_missing(self, monkeypatch):
monkeypatch.setitem(sys.modules, "ctypes", None)
assert glibc_version_string_ctypes() is None


@pytest.mark.parametrize('version_info, expected', [
((), (0, 0, 0)),
Expand Down

0 comments on commit 8582f7e

Please sign in to comment.