Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce dependency on ctypes when discovering glibc version. #6678

Merged
merged 9 commits into from Jul 21, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions news/6543.bugfix
@@ -0,0 +1 @@
If possible, the version info for glibc is extracted from ``os`` instead of ``ctypes`` (which may be missing).
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions news/6675.bugfix
@@ -0,0 +1 @@
If possible, the version info for glibc is extracted from ``os`` instead of ``ctypes`` (which may be missing).
31 changes: 29 additions & 2 deletions src/pip/_internal/utils/glibc.py
@@ -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:
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
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
30 changes: 29 additions & 1 deletion tests/unit/test_utils.py
Expand Up @@ -26,7 +26,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_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 @@ -701,6 +704,31 @@ def test_manylinux_check_glibc_version(self):
# Didn't find the warning we were expecting
assert False

def test_glibc_version_string_confstr_fail(self, monkeypatch):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a successful case, too? In addition, it would probably be good to have at least one successful test of glibc_version_string(), which you also modified. (The code for this test can be basically the same as the one for glibc_version_string_confstr().)


def raises(error):
raise error()

monkeypatch.setattr(
os, "confstr", lambda x: raises(ValueError), raising=False,
)
assert glibc_version_string_confstr() is None

monkeypatch.setattr(
os, "confstr", lambda x: raises(OSError), raising=False,
)
assert glibc_version_string_confstr() is None

monkeypatch.setattr(os, "confstr", lambda x: "XXX", raising=False)
assert glibc_version_string_confstr() is None
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved

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

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


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