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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
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
38 changes: 37 additions & 1 deletion tests/unit/test_utils.py
Original file line number Diff line number Diff line change
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, 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 @@ -668,6 +671,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 @@ -701,6 +708,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
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved

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', [
(None, None),
Expand Down