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%)
This commit is contained in:
Victor Stinner 2018-12-05 14:04:52 +01:00 committed by GitHub
parent 2a893430c9
commit 476b113ed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 6 deletions

View File

@ -169,7 +169,7 @@ _libc_search = re.compile(b'(__libc_init)'
b'|'
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
(which defaults to the Python interpreter) is linked against.
@ -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.
"""
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
if hasattr(os.path, 'realpath'):
# Python 2.2 introduced os.path.realpath(); it is used

View File

@ -3,7 +3,9 @@ import platform
import subprocess
import sys
import sysconfig
import tempfile
import unittest
from unittest import mock
from test import support
@ -263,19 +265,46 @@ class PlatformTest(unittest.TestCase):
self.assertEqual(sts, 0)
def test_libc_ver(self):
# check that libc_ver(executable) doesn't raise an exception
if os.path.isdir(sys.executable) and \
os.path.exists(sys.executable+'.exe'):
# Cygwin horror
executable = sys.executable + '.exe'
else:
executable = sys.executable
res = platform.libc_ver(executable)
platform.libc_ver(executable)
self.addCleanup(support.unlink, support.TESTFN)
with open(support.TESTFN, 'wb') as f:
f.write(b'x'*(16384-10))
filename = support.TESTFN
self.addCleanup(support.unlink, filename)
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')
self.assertEqual(platform.libc_ver(support.TESTFN),
self.assertEqual(platform.libc_ver(filename, chunksize=chunksize),
('glibc', '1.23.4'))
@support.cpython_only

View File

@ -0,0 +1,2 @@
:func:`platform.libc_ver` now uses ``os.confstr('CS_GNU_LIBC_VERSION')`` if
available and the *executable* parameter is not set.