diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1239,7 +1239,7 @@ compiling/linking a program, and when the program is run. The purpose of the :func:`find_library` function is to locate a library in a way -similar to what the compiler does (on platforms with several versions of a +similar to what the compiler/linker does (on platforms with several versions of a shared library the most recent should be loaded), while the ctypes library loaders act like when a program is run, and call the runtime loader directly. @@ -1259,8 +1259,14 @@ The exact functionality is system dependent. On Linux, :func:`find_library` tries to run external programs -(``/sbin/ldconfig``, ``gcc``, and ``objdump``) to find the library file. It -returns the filename of the library file. Here are some examples:: +(``/sbin/ldconfig``, ``gcc``, ``objdump`` and ``ld``) to find the library file. +It returns the filename of the library file. + +.. versionchanged:: 3.6 + On Linux, the value of the environment variable ``LD_LIBRARY_PATH`` is used + when searching for libraries, if a library cannot be found by any other means. + +Here are some examples:: >>> from ctypes.util import find_library >>> find_library("m") diff --git a/Lib/ctypes/test/test_find.py b/Lib/ctypes/test/test_find.py --- a/Lib/ctypes/test/test_find.py +++ b/Lib/ctypes/test/test_find.py @@ -69,6 +69,58 @@ self.assertFalse(os.path.lexists(test.support.TESTFN)) self.assertIsNone(result) + +@unittest.skipUnless(sys.platform.startswith('linux'), + 'Test only valid for Linux') +class LibPathFindTest(unittest.TestCase): + def test_find_on_libpath(self): + import subprocess + import tempfile + + p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, _ = p.communicate() + if p.returncode != 0: + raise unittest.SkipTest('gcc, needed for test, not available') + with tempfile.TemporaryDirectory() as d: + # create an empty temporary file + srcname = os.path.join(d, 'dummy.c') + dstname = os.path.join(d, 'libdummy.so') + with open(srcname, 'w') as f: + pass + self.assertTrue(os.path.exists(srcname)) + # compile the file to a shared library + cmd = ['gcc', '-o', dstname, '--shared', + '-Wl,-soname,libdummy.so', srcname] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, _ = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertTrue(os.path.exists(dstname)) + # now check that libdummy.so can't be found (since not in + # LD_LIBRARY_PATH) + self.assertTrue(find_library('dummy') is None) + # now add the location to LD_LIBRARY_PATH + KEY = 'LD_LIBRARY_PATH' + if KEY not in os.environ: + existed = False + os.environ[KEY] = d + else: + existed = True + old_path = os.environ[KEY] + os.environ[KEY] = '%s:%s' % (old_path, d) + try: + # now check that libdummy.so can be found (since in + # LD_LIBRARY_PATH) + self.assertTrue(find_library('dummy') == 'libdummy.so') + finally: + # restore environment + if not existed: + del os.environ[KEY] + else: + os.environ[KEY] = old_path + + # On platforms where the default shared library suffix is '.so', # at least some libraries can be loaded as attributes of the cdll # object, since ctypes now tries loading the lib again diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -285,8 +285,31 @@ except OSError: pass + def _findLib_ld(name): + # See issue #9998 for why this is needed + expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name) + cmd = ['ld', '-t'] + libpath = os.environ.get('LD_LIBRARY_PATH') + if libpath: + for d in libpath.split(':'): + cmd.extend(['-L', d]) + cmd.extend(['-o', os.devnull, '-l%s' % name]) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=os.environ) + out, _ = p.communicate() + import locale + encoding = locale.getpreferredencoding() + res = re.search(expr, out.decode(encoding)) + if not res: + result = None + else: + result = res.group(0) + return result + def find_library(name): - return _findSoname_ldconfig(name) or _get_soname(_findLib_gcc(name)) + # See issue #9998 + return _findSoname_ldconfig(name) or \ + _get_soname(_findLib_gcc(name) or _findLib_ld(name)) ################################################################ # test code