Skip to content

Commit 82df3b3

Browse files
committed
Closes #9998: Allowed find_library to search additional locations for libraries.
1 parent 48e4bd6 commit 82df3b3

File tree

4 files changed

+82
-6
lines changed

4 files changed

+82
-6
lines changed

Doc/library/ctypes.rst

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,9 +1239,10 @@ When programming in a compiled language, shared libraries are accessed when
12391239
compiling/linking a program, and when the program is run.
12401240

12411241
The purpose of the :func:`find_library` function is to locate a library in a way
1242-
similar to what the compiler does (on platforms with several versions of a
1243-
shared library the most recent should be loaded), while the ctypes library
1244-
loaders act like when a program is run, and call the runtime loader directly.
1242+
similar to what the compiler or runtime loader does (on platforms with several
1243+
versions of a shared library the most recent should be loaded), while the ctypes
1244+
library loaders act like when a program is run, and call the runtime loader
1245+
directly.
12451246

12461247
The :mod:`ctypes.util` module provides a function which can help to determine
12471248
the library to load.
@@ -1259,8 +1260,14 @@ the library to load.
12591260
The exact functionality is system dependent.
12601261

12611262
On Linux, :func:`find_library` tries to run external programs
1262-
(``/sbin/ldconfig``, ``gcc``, and ``objdump``) to find the library file. It
1263-
returns the filename of the library file. Here are some examples::
1263+
(``/sbin/ldconfig``, ``gcc``, ``objdump`` and ``ld``) to find the library file.
1264+
It returns the filename of the library file.
1265+
1266+
.. versionchanged:: 3.6
1267+
On Linux, the value of the environment variable ``LD_LIBRARY_PATH`` is used
1268+
when searching for libraries, if a library cannot be found by any other means.
1269+
1270+
Here are some examples::
12641271

12651272
>>> from ctypes.util import find_library
12661273
>>> find_library("m")

Lib/ctypes/test/test_find.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,49 @@ def test_shell_injection(self):
6969
self.assertFalse(os.path.lexists(test.support.TESTFN))
7070
self.assertIsNone(result)
7171

72+
73+
@unittest.skipUnless(sys.platform.startswith('linux'),
74+
'Test only valid for Linux')
75+
class LibPathFindTest(unittest.TestCase):
76+
def test_find_on_libpath(self):
77+
import subprocess
78+
import tempfile
79+
80+
try:
81+
p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE,
82+
stderr=subprocess.DEVNULL)
83+
out, _ = p.communicate()
84+
except OSError:
85+
raise unittest.SkipTest('gcc, needed for test, not available')
86+
with tempfile.TemporaryDirectory() as d:
87+
# create an empty temporary file
88+
srcname = os.path.join(d, 'dummy.c')
89+
libname = 'py_ctypes_test_dummy'
90+
dstname = os.path.join(d, 'lib%s.so' % libname)
91+
with open(srcname, 'w') as f:
92+
pass
93+
self.assertTrue(os.path.exists(srcname))
94+
# compile the file to a shared library
95+
cmd = ['gcc', '-o', dstname, '--shared',
96+
'-Wl,-soname,lib%s.so' % libname, srcname]
97+
out = subprocess.check_output(cmd)
98+
self.assertTrue(os.path.exists(dstname))
99+
# now check that the .so can't be found (since not in
100+
# LD_LIBRARY_PATH)
101+
self.assertIsNone(find_library(libname))
102+
# now add the location to LD_LIBRARY_PATH
103+
with test.support.EnvironmentVarGuard() as env:
104+
KEY = 'LD_LIBRARY_PATH'
105+
if KEY not in env:
106+
v = d
107+
else:
108+
v = '%s:%s' % (env[KEY], d)
109+
env.set(KEY, v)
110+
# now check that the .so can be found (since in
111+
# LD_LIBRARY_PATH)
112+
self.assertEqual(find_library(libname), 'lib%s.so' % libname)
113+
114+
72115
# On platforms where the default shared library suffix is '.so',
73116
# at least some libraries can be loaded as attributes of the cdll
74117
# object, since ctypes now tries loading the lib again

Lib/ctypes/util.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,32 @@ def _findSoname_ldconfig(name):
285285
except OSError:
286286
pass
287287

288+
def _findLib_ld(name):
289+
# See issue #9998 for why this is needed
290+
expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
291+
cmd = ['ld', '-t']
292+
libpath = os.environ.get('LD_LIBRARY_PATH')
293+
if libpath:
294+
for d in libpath.split(':'):
295+
cmd.extend(['-L', d])
296+
cmd.extend(['-o', os.devnull, '-l%s' % name])
297+
result = None
298+
try:
299+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
300+
stderr=subprocess.PIPE,
301+
universal_newlines=True)
302+
out, _ = p.communicate()
303+
res = re.search(expr, os.fsdecode(out))
304+
if res:
305+
result = res.group(0)
306+
except Exception as e:
307+
pass # result will be None
308+
return result
309+
288310
def find_library(name):
289-
return _findSoname_ldconfig(name) or _get_soname(_findLib_gcc(name))
311+
# See issue #9998
312+
return _findSoname_ldconfig(name) or \
313+
_get_soname(_findLib_gcc(name) or _findLib_ld(name))
290314

291315
################################################################
292316
# test code

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Issue #9998: On Linux, ctypes.util.find_library now looks in LD_LIBRARY_PATH
17+
for shared libraries.
1618

1719
What's New in Python 3.6.0 alpha 4
1820
==================================

0 commit comments

Comments
 (0)