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:
parent
2a893430c9
commit
476b113ed8
|
@ -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):
|
||||||
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
|
||||||
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):
|
||||||
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
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue