Skip to content

Commit 8582f7e

Browse files
brandtbucherpradyunsg
authored andcommitted
Reduce dependency on ctypes when discovering glibc version. (#6678)
1 parent e308497 commit 8582f7e

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

news/6543.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prefer ``os.confstr`` to ``ctypes`` when extracting glibc version info.

news/6675.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prefer ``os.confstr`` to ``ctypes`` when extracting glibc version info.

src/pip/_internal/utils/glibc.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import absolute_import
22

3-
import ctypes
3+
import os
44
import re
55
import warnings
66

@@ -13,6 +13,33 @@
1313
def glibc_version_string():
1414
# type: () -> Optional[str]
1515
"Returns glibc version string, or None if not using glibc."
16+
return glibc_version_string_confstr() or glibc_version_string_ctypes()
17+
18+
19+
def glibc_version_string_confstr():
20+
# type: () -> Optional[str]
21+
"Primary implementation of glibc_version_string using os.confstr."
22+
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
23+
# to be broken or missing. This strategy is used in the standard library
24+
# platform module:
25+
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
26+
try:
27+
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
28+
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
29+
except (AttributeError, OSError, ValueError):
30+
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
31+
return None
32+
return version
33+
34+
35+
def glibc_version_string_ctypes():
36+
# type: () -> Optional[str]
37+
"Fallback implementation of glibc_version_string using ctypes."
38+
39+
try:
40+
import ctypes
41+
except ImportError:
42+
return None
1643

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

5784
def have_compatible_glibc(required_major, minimum_minor):
5885
# type: (int, int) -> bool
59-
version_str = glibc_version_string() # type: Optional[str]
86+
version_str = glibc_version_string()
6087
if version_str is None:
6188
return False
6289
return check_glibc_version(version_str, required_major, minimum_minor)

tests/unit/test_utils.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
)
2828
from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated
2929
from pip._internal.utils.encoding import BOMS, auto_decode
30-
from pip._internal.utils.glibc import check_glibc_version
30+
from pip._internal.utils.glibc import (
31+
check_glibc_version, glibc_version_string, glibc_version_string_confstr,
32+
glibc_version_string_ctypes,
33+
)
3134
from pip._internal.utils.hashes import Hashes, MissingHashes
3235
from pip._internal.utils.misc import (
3336
call_subprocess, egg_link_path, ensure_dir, format_command_args,
@@ -704,6 +707,10 @@ def raising_mkdir(*args, **kwargs):
704707
pass
705708

706709

710+
def raises(error):
711+
raise error
712+
713+
707714
class TestGlibc(object):
708715
def test_manylinux_check_glibc_version(self):
709716
"""
@@ -737,6 +744,35 @@ def test_manylinux_check_glibc_version(self):
737744
# Didn't find the warning we were expecting
738745
assert False
739746

747+
def test_glibc_version_string(self, monkeypatch):
748+
monkeypatch.setattr(
749+
os, "confstr", lambda x: "glibc 2.20", raising=False,
750+
)
751+
assert glibc_version_string() == "2.20"
752+
753+
def test_glibc_version_string_confstr(self, monkeypatch):
754+
monkeypatch.setattr(
755+
os, "confstr", lambda x: "glibc 2.20", raising=False,
756+
)
757+
assert glibc_version_string_confstr() == "2.20"
758+
759+
@pytest.mark.parametrize("failure", [
760+
lambda x: raises(ValueError),
761+
lambda x: raises(OSError),
762+
lambda x: "XXX",
763+
])
764+
def test_glibc_version_string_confstr_fail(self, monkeypatch, failure):
765+
monkeypatch.setattr(os, "confstr", failure, raising=False)
766+
assert glibc_version_string_confstr() is None
767+
768+
def test_glibc_version_string_confstr_missing(self, monkeypatch):
769+
monkeypatch.delattr(os, "confstr", raising=False)
770+
assert glibc_version_string_confstr() is None
771+
772+
def test_glibc_version_string_ctypes_missing(self, monkeypatch):
773+
monkeypatch.setitem(sys.modules, "ctypes", None)
774+
assert glibc_version_string_ctypes() is None
775+
740776

741777
@pytest.mark.parametrize('version_info, expected', [
742778
((), (0, 0, 0)),

0 commit comments

Comments
 (0)