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