Skip to content

Commit 476b113

Browse files
authored
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%)
1 parent 2a89343 commit 476b113

File tree

3 files changed

+51
-7
lines changed

3 files changed

+51
-7
lines changed

Lib/platform.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def _comparable_version(version):
169169
b'|'
170170
br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
171171

172-
def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384):
172+
def libc_ver(executable=None, lib='', version='', chunksize=16384):
173173

174174
""" Tries to determine the libc version that the file executable
175175
(which defaults to the Python interpreter) is linked against.
@@ -184,6 +184,19 @@ def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384):
184184
The file is read and scanned in chunks of chunksize bytes.
185185
186186
"""
187+
if executable is None:
188+
try:
189+
ver = os.confstr('CS_GNU_LIBC_VERSION')
190+
# parse 'glibc 2.28' as ('glibc', '2.28')
191+
parts = ver.split(maxsplit=1)
192+
if len(parts) == 2:
193+
return tuple(parts)
194+
except (AttributeError, ValueError, OSError):
195+
# os.confstr() or CS_GNU_LIBC_VERSION value not available
196+
pass
197+
198+
executable = sys.executable
199+
187200
V = _comparable_version
188201
if hasattr(os.path, 'realpath'):
189202
# Python 2.2 introduced os.path.realpath(); it is used

Lib/test/test_platform.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import subprocess
44
import sys
55
import sysconfig
6+
import tempfile
67
import unittest
8+
from unittest import mock
79

810
from test import support
911

@@ -263,19 +265,46 @@ def test_mac_ver_with_fork(self):
263265
self.assertEqual(sts, 0)
264266

265267
def test_libc_ver(self):
268+
# check that libc_ver(executable) doesn't raise an exception
266269
if os.path.isdir(sys.executable) and \
267270
os.path.exists(sys.executable+'.exe'):
268271
# Cygwin horror
269272
executable = sys.executable + '.exe'
270273
else:
271274
executable = sys.executable
272-
res = platform.libc_ver(executable)
273-
274-
self.addCleanup(support.unlink, support.TESTFN)
275-
with open(support.TESTFN, 'wb') as f:
276-
f.write(b'x'*(16384-10))
275+
platform.libc_ver(executable)
276+
277+
filename = support.TESTFN
278+
self.addCleanup(support.unlink, filename)
279+
280+
with mock.patch('os.confstr', create=True, return_value='mock 1.0'):
281+
# test os.confstr() code path
282+
self.assertEqual(platform.libc_ver(), ('mock', '1.0'))
283+
284+
# test the different regular expressions
285+
for data, expected in (
286+
(b'__libc_init', ('libc', '')),
287+
(b'GLIBC_2.9', ('glibc', '2.9')),
288+
(b'libc.so.1.2.5', ('libc', '1.2.5')),
289+
(b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')),
290+
(b'', ('', '')),
291+
):
292+
with open(filename, 'wb') as fp:
293+
fp.write(b'[xxx%sxxx]' % data)
294+
fp.flush()
295+
296+
# os.confstr() must not be used if executable is set
297+
self.assertEqual(platform.libc_ver(executable=filename),
298+
expected)
299+
300+
# binary containing multiple versions: get the most recent,
301+
# make sure that 1.9 is seen as older than 1.23.4
302+
chunksize = 16384
303+
with open(filename, 'wb') as f:
304+
# test match at chunk boundary
305+
f.write(b'x'*(chunksize - 10))
277306
f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0')
278-
self.assertEqual(platform.libc_ver(support.TESTFN),
307+
self.assertEqual(platform.libc_ver(filename, chunksize=chunksize),
279308
('glibc', '1.23.4'))
280309

281310
@support.cpython_only
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`platform.libc_ver` now uses ``os.confstr('CS_GNU_LIBC_VERSION')`` if
2+
available and the *executable* parameter is not set.

0 commit comments

Comments
 (0)