• 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 == "darwin":
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 os.name == "posix":
93    # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
94    import re, tempfile
95
96    def _is_elf(filename):
97        "Return True if the given file is an ELF file"
98        elf_header = b'\x7fELF'
99        with open(filename, 'br') as thefile:
100            return thefile.read(4) == elf_header
101
102    def _findLib_gcc(name):
103        # Run GCC's linker with the -t (aka --trace) option and examine the
104        # library name it prints out. The GCC command will fail because we
105        # haven't supplied a proper program with main(), but that does not
106        # matter.
107        expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
108
109        c_compiler = shutil.which('gcc')
110        if not c_compiler:
111            c_compiler = shutil.which('cc')
112        if not c_compiler:
113            # No C compiler available, give up
114            return None
115
116        temp = tempfile.NamedTemporaryFile()
117        try:
118            args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
119
120            env = dict(os.environ)
121            env['LC_ALL'] = 'C'
122            env['LANG'] = 'C'
123            try:
124                proc = subprocess.Popen(args,
125                                        stdout=subprocess.PIPE,
126                                        stderr=subprocess.STDOUT,
127                                        env=env)
128            except OSError:  # E.g. bad executable
129                return None
130            with proc:
131                trace = proc.stdout.read()
132        finally:
133            try:
134                temp.close()
135            except FileNotFoundError:
136                # Raised if the file was already removed, which is the normal
137                # behaviour of GCC if linking fails
138                pass
139        res = re.findall(expr, trace)
140        if not res:
141            return None
142
143        for file in res:
144            # Check if the given file is an elf file: gcc can report
145            # some files that are linker scripts and not actual
146            # shared objects. See bpo-41976 for more details
147            if not _is_elf(file):
148                continue
149            return os.fsdecode(file)
150
151
152    if sys.platform == "sunos5":
153        # use /usr/ccs/bin/dump on solaris
154        def _get_soname(f):
155            if not f:
156                return None
157
158            try:
159                proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
160                                        stdout=subprocess.PIPE,
161                                        stderr=subprocess.DEVNULL)
162            except OSError:  # E.g. command not found
163                return None
164            with proc:
165                data = proc.stdout.read()
166            res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
167            if not res:
168                return None
169            return os.fsdecode(res.group(1))
170    else:
171        def _get_soname(f):
172            # assuming GNU binutils / ELF
173            if not f:
174                return None
175            objdump = shutil.which('objdump')
176            if not objdump:
177                # objdump is not available, give up
178                return None
179
180            try:
181                proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
182                                        stdout=subprocess.PIPE,
183                                        stderr=subprocess.DEVNULL)
184            except OSError:  # E.g. bad executable
185                return None
186            with proc:
187                dump = proc.stdout.read()
188            res = re.search(br'\sSONAME\s+([^\s]+)', dump)
189            if not res:
190                return None
191            return os.fsdecode(res.group(1))
192
193    if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
194
195        def _num_version(libname):
196            # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
197            parts = libname.split(b".")
198            nums = []
199            try:
200                while parts:
201                    nums.insert(0, int(parts.pop()))
202            except ValueError:
203                pass
204            return nums or [sys.maxsize]
205
206        def find_library(name):
207            ename = re.escape(name)
208            expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
209            expr = os.fsencode(expr)
210
211            try:
212                proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
213                                        stdout=subprocess.PIPE,
214                                        stderr=subprocess.DEVNULL)
215            except OSError:  # E.g. command not found
216                data = b''
217            else:
218                with proc:
219                    data = proc.stdout.read()
220
221            res = re.findall(expr, data)
222            if not res:
223                return _get_soname(_findLib_gcc(name))
224            res.sort(key=_num_version)
225            return os.fsdecode(res[-1])
226
227    elif sys.platform == "sunos5":
228
229        def _findLib_crle(name, is64):
230            if not os.path.exists('/usr/bin/crle'):
231                return None
232
233            env = dict(os.environ)
234            env['LC_ALL'] = 'C'
235
236            if is64:
237                args = ('/usr/bin/crle', '-64')
238            else:
239                args = ('/usr/bin/crle',)
240
241            paths = None
242            try:
243                proc = subprocess.Popen(args,
244                                        stdout=subprocess.PIPE,
245                                        stderr=subprocess.DEVNULL,
246                                        env=env)
247            except OSError:  # E.g. bad executable
248                return None
249            with proc:
250                for line in proc.stdout:
251                    line = line.strip()
252                    if line.startswith(b'Default Library Path (ELF):'):
253                        paths = os.fsdecode(line).split()[4]
254
255            if not paths:
256                return None
257
258            for dir in paths.split(":"):
259                libfile = os.path.join(dir, "lib%s.so" % name)
260                if os.path.exists(libfile):
261                    return libfile
262
263            return None
264
265        def find_library(name, is64 = False):
266            return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
267
268    else:
269
270        def _findSoname_ldconfig(name):
271            import struct
272            if struct.calcsize('l') == 4:
273                machine = os.uname().machine + '-32'
274            else:
275                machine = os.uname().machine + '-64'
276            mach_map = {
277                'x86_64-64': 'libc6,x86-64',
278                'ppc64-64': 'libc6,64bit',
279                'sparc64-64': 'libc6,64bit',
280                's390x-64': 'libc6,64bit',
281                'ia64-64': 'libc6,IA-64',
282                }
283            abi_type = mach_map.get(machine, 'libc6')
284
285            # XXX assuming GLIBC's ldconfig (with option -p)
286            regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
287            regex = os.fsencode(regex % (re.escape(name), abi_type))
288            try:
289                with subprocess.Popen(['/sbin/ldconfig', '-p'],
290                                      stdin=subprocess.DEVNULL,
291                                      stderr=subprocess.DEVNULL,
292                                      stdout=subprocess.PIPE,
293                                      env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
294                    res = re.search(regex, p.stdout.read())
295                    if res:
296                        return os.fsdecode(res.group(1))
297            except OSError:
298                pass
299
300        def _findLib_ld(name):
301            # See issue #9998 for why this is needed
302            expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
303            cmd = ['ld', '-t']
304            libpath = os.environ.get('LD_LIBRARY_PATH')
305            if libpath:
306                for d in libpath.split(':'):
307                    cmd.extend(['-L', d])
308            cmd.extend(['-o', os.devnull, '-l%s' % name])
309            result = None
310            try:
311                p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
312                                     stderr=subprocess.PIPE,
313                                     universal_newlines=True)
314                out, _ = p.communicate()
315                res = re.findall(expr, os.fsdecode(out))
316                for file in res:
317                    # Check if the given file is an elf file: gcc can report
318                    # some files that are linker scripts and not actual
319                    # shared objects. See bpo-41976 for more details
320                    if not _is_elf(file):
321                        continue
322                    return os.fsdecode(file)
323            except Exception:
324                pass  # result will be None
325            return result
326
327        def find_library(name):
328            # See issue #9998
329            return _findSoname_ldconfig(name) or \
330                   _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
331
332################################################################
333# test code
334
335def test():
336    from ctypes import cdll
337    if os.name == "nt":
338        print(cdll.msvcrt)
339        print(cdll.load("msvcrt"))
340        print(find_library("msvcrt"))
341
342    if os.name == "posix":
343        # find and load_version
344        print(find_library("m"))
345        print(find_library("c"))
346        print(find_library("bz2"))
347
348        # load
349        if sys.platform == "darwin":
350            print(cdll.LoadLibrary("libm.dylib"))
351            print(cdll.LoadLibrary("libcrypto.dylib"))
352            print(cdll.LoadLibrary("libSystem.dylib"))
353            print(cdll.LoadLibrary("System.framework/System"))
354        # issue-26439 - fix broken test call for AIX
355        elif sys.platform.startswith("aix"):
356            from ctypes import CDLL
357            if sys.maxsize < 2**32:
358                print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}")
359                print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}")
360                # librpm.so is only available as 32-bit shared library
361                print(find_library("rpm"))
362                print(cdll.LoadLibrary("librpm.so"))
363            else:
364                print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}")
365                print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}")
366            print(f"crypt\t:: {find_library('crypt')}")
367            print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}")
368            print(f"crypto\t:: {find_library('crypto')}")
369            print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}")
370        else:
371            print(cdll.LoadLibrary("libm.so"))
372            print(cdll.LoadLibrary("libcrypt.so"))
373            print(find_library("crypt"))
374
375if __name__ == "__main__":
376    test()
377