Commit 476b113e authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

bpo-35389: platform.libc_ver() uses os.confstr() (GH-10891)

platform.libc_ver() now uses os.confstr('CS_GNU_LIBC_VERSION') if
available and the *executable* parameter is not set. The default
value of the libc_ver() *executable* parameter becomes None.

Quick benchmark on Fedora 29:

python3 -m perf command ./python -S -c 'import platform; platform.libc_ver()'
94.9 ms +- 4.3 ms -> 33.2 ms +- 1.4 ms: 2.86x faster (-65%)
parent 2a893430
...@@ -169,7 +169,7 @@ _libc_search = re.compile(b'(__libc_init)' ...@@ -169,7 +169,7 @@ _libc_search = re.compile(b'(__libc_init)'
b'|' b'|'
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII) br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384): def libc_ver(executable=None, lib='', version='', chunksize=16384):
""" Tries to determine the libc version that the file executable """ Tries to determine the libc version that the file executable
(which defaults to the Python interpreter) is linked against. (which defaults to the Python interpreter) is linked against.
...@@ -184,6 +184,19 @@ def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384): ...@@ -184,6 +184,19 @@ def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384):
The file is read and scanned in chunks of chunksize bytes. The file is read and scanned in chunks of chunksize bytes.
""" """
if executable is None:
try:
ver = os.confstr('CS_GNU_LIBC_VERSION')
# parse 'glibc 2.28' as ('glibc', '2.28')
parts = ver.split(maxsplit=1)
if len(parts) == 2:
return tuple(parts)
except (AttributeError, ValueError, OSError):
# os.confstr() or CS_GNU_LIBC_VERSION value not available
pass
executable = sys.executable
V = _comparable_version V = _comparable_version
if hasattr(os.path, 'realpath'): if hasattr(os.path, 'realpath'):
# Python 2.2 introduced os.path.realpath(); it is used # Python 2.2 introduced os.path.realpath(); it is used
......
...@@ -3,7 +3,9 @@ import platform ...@@ -3,7 +3,9 @@ import platform
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
import tempfile
import unittest import unittest
from unittest import mock
from test import support from test import support
...@@ -263,19 +265,46 @@ class PlatformTest(unittest.TestCase): ...@@ -263,19 +265,46 @@ class PlatformTest(unittest.TestCase):
self.assertEqual(sts, 0) self.assertEqual(sts, 0)
def test_libc_ver(self): def test_libc_ver(self):
# check that libc_ver(executable) doesn't raise an exception
if os.path.isdir(sys.executable) and \ if os.path.isdir(sys.executable) and \
os.path.exists(sys.executable+'.exe'): os.path.exists(sys.executable+'.exe'):
# Cygwin horror # Cygwin horror
executable = sys.executable + '.exe' executable = sys.executable + '.exe'
else: else:
executable = sys.executable executable = sys.executable
res = platform.libc_ver(executable) platform.libc_ver(executable)
self.addCleanup(support.unlink, support.TESTFN) filename = support.TESTFN
with open(support.TESTFN, 'wb') as f: self.addCleanup(support.unlink, filename)
f.write(b'x'*(16384-10))
with mock.patch('os.confstr', create=True, return_value='mock 1.0'):
# test os.confstr() code path
self.assertEqual(platform.libc_ver(), ('mock', '1.0'))
# test the different regular expressions
for data, expected in (
(b'__libc_init', ('libc', '')),
(b'GLIBC_2.9', ('glibc', '2.9')),
(b'libc.so.1.2.5', ('libc', '1.2.5')),
(b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')),
(b'', ('', '')),
):
with open(filename, 'wb') as fp:
fp.write(b'[xxx%sxxx]' % data)
fp.flush()
# os.confstr() must not be used if executable is set
self.assertEqual(platform.libc_ver(executable=filename),
expected)
# binary containing multiple versions: get the most recent,
# make sure that 1.9 is seen as older than 1.23.4
chunksize = 16384
with open(filename, 'wb') as f:
# test match at chunk boundary
f.write(b'x'*(chunksize - 10))
f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0')
self.assertEqual(platform.libc_ver(support.TESTFN), self.assertEqual(platform.libc_ver(filename, chunksize=chunksize),
('glibc', '1.23.4')) ('glibc', '1.23.4'))
@support.cpython_only @support.cpython_only
......
:func:`platform.libc_ver` now uses ``os.confstr('CS_GNU_LIBC_VERSION')`` if
available and the *executable* parameter is not set.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment