• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import shutil
3import subprocess
4import sys
5
6# find_library(name) returns the pathname of a library, or None.
7if os.name == "nt":
8
9    def _get_build_version():
10        """Return the version of MSVC that was used to build Python.
11
12        For Python 2.3 and up, the version number is included in
13        sys.version.  For earlier versions, assume the compiler is MSVC 6.
14        """
15        # This function was copied from Lib/distutils/msvccompiler.py
16        prefix = "MSC v."
17        i = sys.version.find(prefix)
18        if i == -1:
19            return 6
20        i = i + len(prefix)
21        s, rest = sys.version[i:].split(" ", 1)
22        majorVersion = int(s[:-2]) - 6
23        if majorVersion >= 13:
24            majorVersion += 1
25        minorVersion = int(s[2:3]) / 10.0
26        # I don't think paths are affected by minor version in version 6
27        if majorVersion == 6:
28            minorVersion = 0
29        if majorVersion >= 6:
30            return majorVersion + minorVersion
31        # else we don't know what version of the compiler this is
32        return None
33
34    def find_msvcrt():
35        """Return the name of the VC runtime dll"""
36        version = _get_build_version()
37        if version is None:
38            # better be safe than sorry
39            return None
40        if version <= 6:
41            clibname = 'msvcrt'
42        elif version <= 13:
43            clibname = 'msvcr%d' % (version * 10)
44        else:
45            # CRT is no longer directly loadable. See issue23606 for the
46            # discussion about alternative approaches.
47            return None
48
49        # If python was built with in debug mode
50        import importlib.machinery
51        if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES:
52            clibname += 'd'
53        return clibname+'.dll'
54
55    def find_library(name):
56        if name in ('c', 'm'):
57            return find_msvcrt()
58        # See MSDN for the REAL search order.
59        for directory in os.environ['PATH'].split(os.pathsep):
60            fname = os.path.join(directory, name)
61            if os.path.isfile(fname):
62                return fname
63            if fname.lower().endswith(".dll"):
64                continue
65            fname = fname + ".dll"
66            if os.path.isfile(fname):
67                return fname
68        return None
69
70elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}:
71    from ctypes.macholib.dyld import dyld_find as _dyld_find
72    def find_library(name):
73        possible = ['lib%s.dylib' % name,
74                    '%s.dylib' % name,
75                    '%s.framework/%s' % (name, name)]
76        for name in possible:
77            try:
78                return _dyld_find(name)
79            except ValueError:
80                continue
81        return None
82
83elif sys.platform.startswith("aix"):
84    # AIX has two styles of storing shared libraries
85    # GNU auto_tools refer to these as svr4 and aix
86    # svr4 (System V Release 4) is a regular file, often with .so as suffix
87    # AIX style uses an archive (suffix .a) with members (e.g., shr.o, libssl.so)
88    # see issue#26439 and _aix.py for more details
89
90    from ctypes._aix import find_library
91
92elif sys.platform == "android":
93    def find_library(name):
94        directory = "/system/lib"
95        if "64" in os.uname().machine:
96            directory += "64"
97
98        fname = f"{directory}/lib{name}.so"
99        return fname if os.path.isfile(fname) else None
100
101elif os.name == "posix":
102    # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
103    import re, tempfile
104
105    def _is_elf(filename):
106        "Return True if the given file is an ELF file"
107        elf_header = b'\x7fELF'
108        try:
109            with open(filename, 'br') as thefile:
110                return thefile.read(4) == elf_header
111        except FileNotFoundError:
112            return False
113
114    def _findLib_gcc(name):
115        # Run GCC's linker with the -t (aka --trace) option and examine the
116        # library name it prints out. The GCC command will fail because we
117        # haven't supplied a proper program with main(), but that does not
118        # matter.
119        expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
120
121        c_compiler = shutil.which('gcc')
122        if not c_compiler:
123            c_compiler = shutil.which('cc')
124        if not c_compiler:
125            # No C compiler available, give up
126            return None
127
128        temp = tempfile.NamedTemporaryFile()
129        try:
130            args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
131
132            env = dict(os.environ)
133            env['LC_ALL'] = 'C'
134            env['LANG'] = 'C'
135            try:
136                proc = subprocess.Popen(args,
137                                        stdout=subprocess.PIPE,
138                                        stderr=subprocess.STDOUT,
139                                        env=env)
140            except OSError:  # E.g. bad executable
141                return None
142            with proc:
143                trace = proc.stdout.read()
144        finally:
145            try:
146                temp.close()
147            except FileNotFoundError:
148                # Raised if the file was already removed, which is the normal
149                # behaviour of GCC if linking fails
150                pass
151        res = re.findall(expr, trace)
152        if not res:
153            return None
154
155        for file in res:
156            # Check if the given file is an elf file: gcc can report
157            # some files that are linker scripts and not actual
158            # shared objects. See bpo-41976 for more details
159            if not _is_elf(file):
160                continue
161            return os.fsdecode(file)
162
163
164    if sys.platform == "sunos5":
165        # use /usr/ccs/bin/dump on solaris
166        def _get_soname(f):
167            if not f:
168                return None
169
170            try:
171                proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
172                                        stdout=subprocess.PIPE,
173                                        stderr=subprocess.DEVNULL)
174            except OSError:  # E.g. command not found
175                return None
176            with proc:
177                data = proc.stdout.read()
178            res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
179            if not res:
180                return None
181            return os.fsdecode(res.group(1))
182    else:
183        def _get_soname(f):
184            # assuming GNU binutils / ELF
185            if not f:
186                return None
187            objdump = shutil.which('objdump')
188            if not objdump:
189                # objdump is not available, give up
190                return None
191
192            try:
193                proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
194                                        stdout=subprocess.PIPE,
195                                        stderr=subprocess.DEVNULL)
196            except OSError:  # E.g. bad executable
197                return None
198            with proc:
199                dump = proc.stdout.read()
200            res = re.search(br'\sSONAME\s+([^\s]+)', dump)
201            if not res:
202                return None
203            return os.fsdecode(res.group(1))
204
205    if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
206
207        def _num_version(libname):
208            # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
209            parts = libname.split(b".")
210            nums = []
211            try:
212                while parts:
213                    nums.insert(0, int(parts.pop()))
214            except ValueError:
215                pass
216            return nums or [sys.maxsize]
217
218        def find_library(name):
219            ename = re.escape(name)
220            expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
221            expr = os.fsencode(expr)
222
223            try:
224                proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
225                                        stdout=subprocess.PIPE,
226                                        stderr=subprocess.DEVNULL)
227            except OSError:  # E.g. command not found
228                data = b''
229            else:
230                with proc:
231                    data = proc.stdout.read()
232
233            res = re.findall(expr, data)
234            if not res:
235                return _get_soname(_findLib_gcc(name))
236            res.sort(key=_num_version)
237            return os.fsdecode(res[-1])
238
239    elif sys.platform == "sunos5":
240
241        def _findLib_crle(name, is64):
242            if not os.path.exists('/usr/bin/crle'):
243                return None
244
245            env = dict(os.environ)
246            env['LC_ALL'] = 'C'
247
248            if is64:
249                args = ('/usr/bin/crle', '-64')
250            else:
251                args = ('/usr/bin/crle',)
252
253            paths = None
254            try:
255                proc = subprocess.Popen(args,
256                                        stdout=subprocess.PIPE,
257                                        stderr=subprocess.DEVNULL,
258                                        env=env)
259            except OSError:  # E.g. bad executable
260                return None
261            with proc:
262                for line in proc.stdout:
263                    line = line.strip()
264                    if line.startswith(b'Default Library Path (ELF):'):
265                        paths = os.fsdecode(line).split()[4]
266
267            if not paths:
268                return None
269
270            for dir in paths.split(":"):
271                libfile = os.path.join(dir, "lib%s.so" % name)
272                if os.path.exists(libfile):
273                    return libfile
274
275            return None
276
277        def find_library(name, is64 = False):
278            return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
279
280    else:
281
282        def _findSoname_ldconfig(name):
283            import struct
284            if struct.calcsize('l') == 4:
285                machine = os.uname().machine + '-32'
286            else:
287                machine = os.uname().machine + '-64'
288            mach_map = {
289                'x86_64-64': 'libc6,x86-64',
290                'ppc64-64': 'libc6,64bit',
291                'sparc64-64': 'libc6,64bit',
292                's390x-64': 'libc6,64bit',
293                'ia64-64': 'libc6,IA-64',
294                }
295            abi_type = mach_map.get(machine, 'libc6')
296
297            # XXX assuming GLIBC's ldconfig (with option -p)
298            regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
299            regex = os.fsencode(regex % (re.escape(name), abi_type))
300            try:
301                with subprocess.Popen(['/sbin/ldconfig', '-p'],
302                                      stdin=subprocess.DEVNULL,
303                                      stderr=subprocess.DEVNULL,
304                                      stdout=subprocess.PIPE,
305                                      env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
306                    res = re.search(regex, p.stdout.read())
307                    if res:
308                        return os.fsdecode(res.group(1))
309            except OSError:
310                pass
311
312        def _findLib_ld(name):
313            # See issue #9998 for why this is needed
314            expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
315            cmd = ['ld', '-t']
316            libpath = os.environ.get('LD_LIBRARY_PATH')
317            if libpath:
318                for d in libpath.split(':'):
319                    cmd.extend(['-L', d])
320            cmd.extend(['-o', os.devnull, '-l%s' % name])
321            result = None
322            try:
323                p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
324                                     stderr=subprocess.PIPE,
325                                     universal_newlines=True)
326                out, _ = p.communicate()
327                res = re.findall(expr, os.fsdecode(out))
328                for file in res:
329                    # Check if the given file is an elf file: gcc can report
330                    # some files that are linker scripts and not actual
331                    # shared objects. See bpo-41976 for more details
332                    if not _is_elf(file):
333                        continue
334                    return os.fsdecode(file)
335            except Exception:
336                pass  # result will be None
337            return result
338
339        def find_library(name):
340            # See issue #9998
341            return _findSoname_ldconfig(name) or \
342                   _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
343
344################################################################
345# test code
346
347def test():
348    from ctypes import cdll
349    if os.name == "nt":
350        print(cdll.msvcrt)
351        print(cdll.load("msvcrt"))
352        print(find_library("msvcrt"))
353
354    if os.name == "posix":
355        # find and load_version
356        print(find_library("m"))
357        print(find_library("c"))
358        print(find_library("bz2"))
359
360        # load
361        if sys.platform == "darwin":
362            print(cdll.LoadLibrary("libm.dylib"))
363            print(cdll.LoadLibrary("libcrypto.dylib"))
364            print(cdll.LoadLibrary("libSystem.dylib"))
365            print(cdll.LoadLibrary("System.framework/System"))
366        # issue-26439 - fix broken test call for AIX
367        elif sys.platform.startswith("aix"):
368            from ctypes import CDLL
369            if sys.maxsize < 2**32:
370                print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}")
371                print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}")
372                # librpm.so is only available as 32-bit shared library
373                print(find_library("rpm"))
374                print(cdll.LoadLibrary("librpm.so"))
375            else:
376                print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}")
377                print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}")
378            print(f"crypt\t:: {find_library('crypt')}")
379            print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}")
380            print(f"crypto\t:: {find_library('crypto')}")
381            print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}")
382        else:
383            print(cdll.LoadLibrary("libm.so"))
384            print(cdll.LoadLibrary("libcrypt.so"))
385            print(find_library("crypt"))
386
387if __name__ == "__main__":
388    test()
389