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