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